JVM篇-垃圾收集的相关概念&具体算法使用
发布日期:2021-05-07 20:42:20 浏览次数:24 分类:精选文章

本文共 41828 字,大约阅读时间需要 139 分钟。

一、基本概念

1、标记判断哪些是垃圾(标记阶段)

​ 我们要进行垃圾收集,首先就需要判断哪些是垃圾需要被收集,简单来说就是哪些已经没有被使用到的例如对象,那怎样辨别这些对象呢?一般有两个算法:

1)、引用计数法

​ 就是用一个字段,如果这个对象被其他对象引用了,这个字段就+1,如果没有被引用释放了,就-1,这个字段如果为0,就能被判断为垃圾了。但会存在循环引用的问题,例如两个对象相互引用,但其实这两个对象都已经没有另外的第3个对象引用了,已经是垃圾了。

2)、可达性分析算法(根搜索算法)

​ 可达性算法是由上到下,如果被GC ROOTS根对象集合引用就认为不是垃圾,如果从各个GC Root节点由上到下不能标记到,就认为是垃圾。

​ 那哪些能被作为GC ROOT呢?

​ 例如现在正在被各个线程使用的对象、参数,被类的静态常量引用的内容、被JVM虚拟机内部引用。还有例如在使用分代搜索算法的时候,在回收年轻代的时候,就可以将老年代的对象作为GC ROOT,如果年轻代的对象为老年代引用,就不能判断为垃圾。同时也能使用MAT&JProfiler进行GC ROOT溯源。

2、怎样清除垃圾

​ 通过前面的标记阶段,我们已经分辨出了那些是垃圾了,下面我们需要进行垃圾清除了(垃圾对象与不是垃圾的对象在当前是可能混合在一起的)。

​ JVM的垃圾清除的一些基本算法:

1)、标记-清除算法(Mark-Sweep)

​ 也就是说我们只是在一整块混合区域内将那些是垃圾的清除掉,不是垃圾的对象我们并没有处理,所以这种算法是会产生较多的内存碎片的。

2)、复杂算法(Copying)

​ 这种算法一般将内存分为两块,将存活的对象移动另一块本次没有使用的内存中,这样就不会产生很多内存碎片了,例如JVM中的两块survivor内存from-to的回收。

​ 优点:实现简单,运行高效,不会产生内存碎片问题。缺点:由其的介绍也能看出需要更多的内存。

​ 对其的使用,一般是那些少量对象存活的区域,因为如果大量对象存活的话,就需要复制很多,所以一般在年轻代使用,因为其大部分对象都存活很短。

3)、标记-清除-压缩算法(Mark-Compact)

​ 这个是在前面Mark-Sweep多了一个压缩的过程,也就是解决碎片问题,将剩下的存活对象移动到连续的空间。

​ 优点:它的优点,其实是解决了前面两种的低价,即碎片化、需要更多的内存

​ 缺点:这个也很明显,两者都想要,就会没有前面那两种单独的效率高,由于需要移动对象,所以需要修改引用地址,并且需要暂停用户线程(STW)

3、垃圾收集的一些衍生具体实现

​ 上面是我们一般会使用的垃圾收集的具体实现,但由于这些算法有不同的特点,所以我们需要考虑怎样使用它们,来达到最大的效率

1)、分代算法

​ 由于不同的算法实现又不同的特点,也就是适用不同的场景,所以JVM就将堆内存分为年轻代&老年代,年轻代对象存活率不高所以一般使用复制算法(例如分为三个区:eden、from、to),而老年代就可以使用例如标记清除或者标记清除压缩。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJu9Au8A-1616505071420)(…/图片库/JVM篇-垃圾收集的相关概念&具体算法使用/image-20210321142539737.png)]

​ 然后下面是标记清除压缩

在这里插入图片描述

在这里插入图片描述

​ 上面是标记清除算法,其不会压缩。

2)、增量算法

​ 我们在进行垃圾收集的时候,如果一次全部完成,就会造成本次会耗费大量的在垃圾清理上,一次停留过长的时间(STW)。所以就有了增量收集算法,也就是一次只回收一小块内存以及对收集过程分阶段,让垃圾收集线程与用户线程交替执行,直到完成所有的收集过程,但对垃圾的收集底层还是使用标记清除或复制算法完成的。

​ 缺点:由上面的描叙我们也能知道,在不同线程切换,会增加额外的开销,其是会让整体导读成本升高,造成系统的谈吐量升高。

3)、分区算法

​ 我们在前面有提到分代,也就是说,将一整块大的内存分为年轻代&老年代,而分区算法是G1垃圾收集器的定义,分区算法是将堆划分的更细(这些更小的块也是有分年轻代、老年代,但这些年轻代&老年代的块是混合的,并不是连续的),也就能更好的控制每次需要收集块,同时也能更好的计算每次收集的暂停时间。因此这种算法适合那些大内存的应用,如果是小内存,就没必要分成这样更小的块。

二、垃圾回收器的分类

1、按线程工作数量:可以分为串行(单线程)垃圾回收器、并行(多个线程)垃圾回收器

2、按碎片处理方式:可分为压缩垃圾回收器、非压缩垃圾回收器

3、按工作的空间:可分为年轻代垃圾回收器、老年代垃圾回收器

​ 当然也可以从其他的维度来区分。

三、垃圾回收器的性能指标

1、吞吐量

​ 运行用户代码的时间占总的运行时间的比例(总运行时间 — 程序运行时间+垃圾回收器的运行时间)

2、垃圾收集的开销

​ 垃圾收集器运行的时间与总的比例。

3、暂停时间

​ 执行垃圾收集时,程序工作线程被暂停的时间(这个一般是指一次收集,并不是指完成整个区的全部暂停时间,当然也看这个垃圾收集器会不会一次完成所有收集)

4、收集效率

​ 一个对象从诞生(死亡?)到被回收的时间。

​ 不同的垃圾回收器会有不同的侧重点,一般不会所有都比其他的要好,就像高并发的CAP一样并不能同时让三者都达到最优。

5、所需要的空间

四、垃圾回收器种类与分代之间的关系

enum GCName {  ParallelOld,  SerialOld,  PSMarkSweep,  ParallelScavenge,  DefNew,  ParNew,  G1New,  ConcurrentMarkSweep,  G1Old,  GCNameEndSentinel};

​ 这个就是源码定义的垃圾回收器名称。

​ 年轻代的垃圾回收器:Serial(DefNew)、Parallel Scavenge(ParallelScavenge)、ParNew(ParNew)、G1(G1New)

​ 老年代的垃圾回收器:Serial Old(SerialOld)、Parallel Old(ParallelOld)、CMS(ConcurrentMarkSweep)、G1(G1Old)

​ 而G1是两个代都用其来收集。

五、垃圾回收器的选择

1、JVM关于JVM垃圾选择器的确认&初始化

// Check consistency of GC selectionbool Arguments::check_gc_consistency() {  // Ensure that the user has not selected conflicting sets  // of collectors.  uint i = 0;  if (UseSerialGC)                       i++;  if (UseConcMarkSweepGC)                i++;  if (UseParallelGC || UseParallelOldGC) i++;  if (UseG1GC)                           i++;  if (i > 1) {    jio_fprintf(defaultStream::error_stream(),                "Conflicting collector combinations in option list; "                "please refer to the release notes for the combinations "                "allowed\n");    return false;  }  if (UseConcMarkSweepGC && !UseParNewGC) {    jio_fprintf(defaultStream::error_stream(),        "It is not possible to combine the DefNew young collector with the CMS collector.\n");    return false;  }  if (UseParNewGC && !UseConcMarkSweepGC) {    jio_fprintf(defaultStream::error_stream(),        "It is not possible to combine the ParNew young collector with any collector other than CMS.\n");    return false;  }  return true;}

​ 这个就是JVM对使用垃圾收集器的判断,其可以分为两部分:前面的i++是判断其不能同时使用,而后面的UseConcMarkSweepGCUseParNewGC则是判断这两个需要同时搭配使用,UseParNewGC用于年轻代、UseConcMarkSweepGC用于老年代。

​ 我们再回到以前文章介绍的:

if (UseParallelGC) {    return Universe::create_heap_with_policy
(); } else if (UseG1GC) { return Universe::create_heap_with_policy
(); } else if (UseConcMarkSweepGC) { return Universe::create_heap_with_policy
();#endif } else if (UseSerialGC) { return Universe::create_heap_with_policy
(); }

2、UseConcMarkSweepGC&UseSerialGC垃圾收集器初始化信息

​ 可以看到UseConcMarkSweepGCUseSerialGC是使用相同的Heap,但其Policy策略是不同的:

void ConcurrentMarkSweepPolicy::initialize_generations() {  _young_gen_spec = new GenerationSpec(Generation::ParNew, _initial_young_size,                                       _max_young_size, _gen_alignment);  _old_gen_spec   = new GenerationSpec(Generation::ConcurrentMarkSweep,                                       _initial_old_size, _max_old_size, _gen_alignment);}

​ 可以看到这里也就表明了ParNewConcurrentMarkSweep

void MarkSweepPolicy::initialize_generations() {  _young_gen_spec = new GenerationSpec(Generation::DefNew, _initial_young_size, _max_young_size, _gen_alignment);  _old_gen_spec   = new GenerationSpec(Generation::MarkSweepCompact, _initial_old_size, _max_old_size, _gen_alignment);}
jint GenCollectedHeap::initialize() {	............  ReservedSpace young_rs = heap_rs.first_part(gen_policy()->young_gen_spec()->max_size(), false, false);  _young_gen = gen_policy()->young_gen_spec()->init(young_rs, rem_set());  heap_rs = heap_rs.last_part(gen_policy()->young_gen_spec()->max_size());  ReservedSpace old_rs = heap_rs.first_part(gen_policy()->old_gen_spec()->max_size(), false, false);  _old_gen = gen_policy()->old_gen_spec()->init(old_rs, rem_set()); 	...........  return JNI_OK;}
Generation* _young_gen;Generation* _old_gen;

这些代码我们在前面文章也有贴,下面我们来其的init()方法调用:

Generation* GenerationSpec::init(ReservedSpace rs, CardTableRS* remset) {  switch (name()) {    case Generation::DefNew:      return new DefNewGeneration(rs, init_size());    case Generation::MarkSweepCompact:      return new TenuredGeneration(rs, init_size(), remset);#if INCLUDE_ALL_GCS    case Generation::ParNew:      return new ParNewGeneration(rs, init_size());    case Generation::ConcurrentMarkSweep: {      assert(UseConcMarkSweepGC, "UseConcMarkSweepGC should be set");      if (remset == NULL) {        vm_exit_during_initialization("Rem set incompatibility.");      }      ConcurrentMarkSweepGeneration* g = NULL;      g = new ConcurrentMarkSweepGeneration(rs, init_size(), remset);      g->initialize_performance_counters();      return g;    }#endif // INCLUDE_ALL_GCS    default:      guarantee(false, "unrecognized GenerationName");      return NULL;  }}

​ 这些收集器在年轻代、年轻代的选择主要就是在这里对对应的代空间Generation进行初始化。

class TenuredGeneration: public CardGeneration {  	.......  // Printing  const char* name() const { return "tenured generation"; }  const char* short_name() const { return "Tenured"; }
class DefNewGeneration: public Generation {		............  // Printing  virtual const char* name() const;  virtual const char* short_name() const { return "DefNew"; }
// A Generation that does parallel young-gen collection.class ParNewGeneration: public DefNewGeneration {	......  virtual const char* name() const;  virtual const char* short_name() const { return "ParNew"; }

​ 可以看到ParNewGeneration是继承的DefNewGeneration

class ConcurrentMarkSweepGeneration: public CardGeneration {	......  // Printing  const char* name() const;  virtual const char* short_name() const { return "CMS"; }

上面这些Generation主要是UseSerialGCUseConcMarkSweepGC

3、UseParallelGC相关的信息

UseParallelGC则是:

jint ParallelScavengeHeap::initialize() {	.......  _gens = new AdjoiningGenerations(heap_rs, _collector_policy, generation_alignment());		........  return JNI_OK;}
AdjoiningGenerations::AdjoiningGenerations(ReservedSpace old_young_rs,                                           GenerationSizer* policy,                                           size_t alignment) : 	........    _young_gen = new PSYoungGen(init_high_byte_size,                                min_high_byte_size,                                max_high_byte_size);    _old_gen = new PSOldGen(init_low_byte_size,                            min_low_byte_size,                            max_low_byte_size,                            "old", 1);  	.....}

​ 其用的代概念对象是PSYoungGenPSOldGen

class PSYoungGen : public CHeapObj
{ ............ // Spaces MutableSpace* _eden_space; MutableSpace* _from_space; MutableSpace* _to_space; ......... virtual const char* name() const { return "PSYoungGen"; }

​ 而对于其的name()选择:

inline const char* PSOldGen::select_name() {  return UseParallelOldGC ? "ParOldGen" : "PSOldGen";}
class PSOldGen : public CHeapObj
{ .......... // Printing support virtual const char* name() const { return _name; }

六、垃圾收集器的管理对象

void MemoryService::set_universe_heap(CollectedHeap* heap) {  CollectedHeap::Name kind = heap->kind();  switch (kind) {    case CollectedHeap::GenCollectedHeap : {      add_gen_collected_heap_info(GenCollectedHeap::heap());      break;    }#if INCLUDE_ALL_GCS    case CollectedHeap::ParallelScavengeHeap : {      add_parallel_scavenge_heap_info(ParallelScavengeHeap::heap());      break;    }    case CollectedHeap::G1CollectedHeap : {      add_g1_heap_info(G1CollectedHeap::heap());      break;    }#endif // INCLUDE_ALL_GCS    default: {      guarantee(false, "Unrecognized kind of heap");    }  }	.........}

1、CollectedHeap::GenCollectedHeap

​ 这个是CMSSerial收集器使用的堆。

1)、add_gen_collected_heap_info

// Add memory pools for GenCollectedHeap// This function currently only supports two generations collected heap.// The collector for GenCollectedHeap will have two memory managers.void MemoryService::add_gen_collected_heap_info(GenCollectedHeap* heap) {  CollectorPolicy* policy = heap->collector_policy();  GenCollectorPolicy* gen_policy = policy->as_generation_policy();  if (gen_policy != NULL) {    Generation::Name kind = gen_policy->young_gen_spec()->name();    switch (kind) {      case Generation::DefNew:        _minor_gc_manager = MemoryManager::get_copy_memory_manager();        break;#if INCLUDE_ALL_GCS      case Generation::ParNew:        _minor_gc_manager = MemoryManager::get_parnew_memory_manager();        break;#endif // INCLUDE_ALL_GCS      default:        break;    }    if (policy->is_mark_sweep_policy()) {      _major_gc_manager = MemoryManager::get_msc_memory_manager();#if INCLUDE_ALL_GCS    } else if (policy->is_concurrent_mark_sweep_policy()) {      _major_gc_manager = MemoryManager::get_cms_memory_manager();#endif // INCLUDE_ALL_GCS	.........  add_generation_memory_pool(heap->young_gen(), _major_gc_manager, _minor_gc_manager);  add_generation_memory_pool(heap->old_gen(), _major_gc_manager);}

这里就是通过Heap,来选择对应的内存管理、监控,例如使用的jconsoleVisualVM这些工具应该就是面向这里初始的一些对象。

​ 可以看到这里首先是选择年轻代的类型:DefNewSerial)、ParNewCMS),然后是选择老年代对应的清理策略:MarkSweepCompact标记清除压缩是SerialConcurrentMarkSweep并发标记清除是CMS。这里的_minor_gc_manager对应的是年轻代、_major_gc_manager是老年代

2)、get_copy_memory_manager()

GCMemoryManager* MemoryManager::get_copy_memory_manager() {  return (GCMemoryManager*) new CopyMemoryManager();}
// These subclasses of GCMemoryManager are defined to include// GC-specific information.// TODO: Add GC-specific informationclass CopyMemoryManager : public GCMemoryManager {private:public:  CopyMemoryManager() : GCMemoryManager() {}  const char* name() { return "Copy"; }};

这里先注意下Managername(),这个到时候我们可以通过jconsole来查看对应年轻代、老年代使用的Manager,这个就是Serial的年轻代

3)、get_parnew_memory_manager()

GCMemoryManager* MemoryManager::get_parnew_memory_manager() {  return (GCMemoryManager*) new ParNewMemoryManager();}
class ParNewMemoryManager : public GCMemoryManager {private:public:  ParNewMemoryManager() : GCMemoryManager() {}  const char* name() { return "ParNew"; }};

​ 这个是CMS的年轻代

4)、get_cms_memory_manager()

GCMemoryManager* MemoryManager::get_cms_memory_manager() {  return (GCMemoryManager*) new CMSMemoryManager();}
class CMSMemoryManager : public GCMemoryManager {private:public:  CMSMemoryManager() : GCMemoryManager() {}  const char* name() { return "ConcurrentMarkSweep";}};

CMS的老年代

5)、get_msc_memory_manager()

GCMemoryManager* MemoryManager::get_msc_memory_manager() {  return (GCMemoryManager*) new MSCMemoryManager();}
class MSCMemoryManager : public GCMemoryManager {private:public:  MSCMemoryManager() : GCMemoryManager() {}  const char* name() { return "MarkSweepCompact"; }};

Serial的老年代,也可以叫为MSC(MarkSweepCompact)。

2、CollectedHeap::ParallelScavengeHeap

1)、add_parallel_scavenge_heap_info

void MemoryService::add_parallel_scavenge_heap_info(ParallelScavengeHeap* heap) {  // Two managers to keep statistics about _minor_gc_manager and _major_gc_manager GC.  _minor_gc_manager = MemoryManager::get_psScavenge_memory_manager();  _major_gc_manager = MemoryManager::get_psMarkSweep_memory_manager();  add_psYoung_memory_pool(heap->young_gen(), _major_gc_manager, _minor_gc_manager);  add_psOld_memory_pool(heap->old_gen(), _major_gc_manager);}

2)、get_psScavenge_memory_manager()

GCMemoryManager* MemoryManager::get_psScavenge_memory_manager() {  return (GCMemoryManager*) new PSScavengeMemoryManager();}
class PSScavengeMemoryManager : public GCMemoryManager {private:public:  PSScavengeMemoryManager() : GCMemoryManager() {}  const char* name() { return "PS Scavenge"; }};

3)、get_psMarkSweep_memory_manager

GCMemoryManager* MemoryManager::get_psMarkSweep_memory_manager() {  return (GCMemoryManager*) new PSMarkSweepMemoryManager();}
class PSMarkSweepMemoryManager : public GCMemoryManager {private:public:  PSMarkSweepMemoryManager() : GCMemoryManager() {}  const char* name() { return "PS MarkSweep"; }};

3、CollectedHeap::G1CollectedHeap

1)、add_g1_heap_info

void MemoryService::add_g1_heap_info(G1CollectedHeap* g1h) {  assert(UseG1GC, "sanity");  _minor_gc_manager = MemoryManager::get_g1YoungGen_memory_manager();  _major_gc_manager = MemoryManager::get_g1OldGen_memory_manager();	........  add_g1YoungGen_memory_pool(g1h, _major_gc_manager, _minor_gc_manager);  add_g1OldGen_memory_pool(g1h, _major_gc_manager);}

2)、get_g1YoungGen_memory_manager

GCMemoryManager* MemoryManager::get_g1YoungGen_memory_manager() {  return (GCMemoryManager*) new G1YoungGenMemoryManager();}
class G1YoungGenMemoryManager : public GCMemoryManager {private:public:  G1YoungGenMemoryManager() : GCMemoryManager() {}  const char* name() { return "G1 Young Generation"; }};

3)、get_g1OldGen_memory_manager

GCMemoryManager* MemoryManager::get_g1OldGen_memory_manager() {  return (GCMemoryManager*) new G1OldGenMemoryManager();}
class G1OldGenMemoryManager : public GCMemoryManager {private:public:  G1OldGenMemoryManager() : GCMemoryManager() {}  const char* name() { return "G1 Old Generation"; }};

七、垃圾收集器的使用

​ 这里是基本的demo

public class MyJvm {    public static void main(String[] args)    {        List
> stringList = new ArrayList<>(); for (;;) { List
afterList = new ArrayList<>(); for (int i = 0; i < 10000; i++) { afterList.add(String.valueOf(Math.random())); } stringList.add(afterList); if (stringList.size() > 100) { stringList.clear(); } } }}

然后JVM中也定义了发生GC的原因:

const char* GCCause::to_string(GCCause::Cause cause) {  switch (cause) {    case _java_lang_system_gc:      return "System.gc()";    case _full_gc_alot:      return "FullGCAlot";    case _scavenge_alot:      return "ScavengeAlot";    case _allocation_profiler:      return "Allocation Profiler";    case _jvmti_force_gc:      return "JvmtiEnv ForceGarbageCollection";    case _gc_locker:      return "GCLocker Initiated GC";    case _heap_inspection:      return "Heap Inspection Initiated GC";    case _heap_dump:      return "Heap Dump Initiated GC";    case _wb_young_gc:      return "WhiteBox Initiated Young GC";    case _wb_conc_mark:      return "WhiteBox Initiated Concurrent Mark";    case _wb_full_gc:      return "WhiteBox Initiated Full GC";    case _update_allocation_context_stats_inc:    case _update_allocation_context_stats_full:      return "Update Allocation Context Stats";    case _no_gc:      return "No GC";    case _allocation_failure:      return "Allocation Failure";    case _tenured_generation_full:      return "Tenured Generation Full";    case _metadata_GC_threshold:      return "Metadata GC Threshold";    case _metadata_GC_clear_soft_refs:      return "Metadata GC Clear Soft References";    case _cms_generation_full:      return "CMS Generation Full";    case _cms_initial_mark:      return "CMS Initial Mark";    case _cms_final_remark:      return "CMS Final Remark";    case _cms_concurrent_mark:      return "CMS Concurrent Mark";    case _old_generation_expanded_on_last_scavenge:      return "Old Generation Expanded On Last Scavenge";    case _old_generation_too_full_to_scavenge:      return "Old Generation Too Full To Scavenge";    case _adaptive_size_policy:      return "Ergonomics";    case _g1_inc_collection_pause:      return "G1 Evacuation Pause";    case _g1_humongous_allocation:      return "G1 Humongous Allocation";    case _dcmd_gc_run:      return "Diagnostic Command";    case _last_gc_cause:      return "ILLEGAL VALUE - last gc cause - ILLEGAL VALUE";    default:      return "unknown GCCause";  }  ShouldNotReachHere();}

1、Serial垃圾回收器

1)、基本介绍

​ Serial收集器是单线程的垃圾收集器,简单而高效(对单CPU来说,因为其没有线程切换的开销)其是最基本的回收器。同时通过UseSerialGC去使用还收集器,年轻代使用的是是CopyGeneration::DefNew)复制算法、老年代使用的是MarkSweepCompactGeneration::MarkSweepCompact)标记清除压缩算法。

​ 我们再结合前面的JVM源码定义的GCName:年轻代使用的垃圾回收器是DefNew、老年代使用的是SerialOld

​ 这个收集器在其运行时会在安全点去暂停所有的用户线程,直到其垃圾回收工作完成(STop The World,需要注意的是,目前的垃圾回收器或多或少都会有此状态)。

在这里插入图片描述

2)、使用及日志打印

​ 我们使用此参数-Xlog:gc* -Xmx128M -Xms128M -XX:+UseSerialGC

​ 再看其运行时的日志打印:

[0.026s][info][gc] Using Serial[0.026s][info][gc,heap,coops] Heap address: 0x00000000f8000000, size: 128 MB, Compressed Oops mode: 32-bit

第一次的收集:

[0.506s][info][gc,start     ] GC(0) Pause Young (Allocation Failure)[0.545s][info][gc,heap      ] GC(0) DefNew: 34944K->4351K(39296K)[0.545s][info][gc,heap      ] GC(0) Tenured: 0K->22920K(87424K)[0.545s][info][gc,metaspace ] GC(0) Metaspace: 5421K->5421K(1056768K)[0.545s][info][gc           ] GC(0) Pause Young (Allocation Failure) 34M->26M(123M) 39.677ms[0.545s][info][gc,cpu       ] GC(0) User=0.03s Sys=0.00s Real=0.04s

​ 然后我们看下最开始的GC:

​ 1、可以看到这里首先是暂停的年轻代Pause Young

​ 2、然后本次进行GC的原因是Allocation Failure

​ 3、GC的变化:DefNew: 34944K->4351K(39296K)Tenured: 0K->22920K(87424K),通过这个变化可以看到,本次年轻代进行回收了,代并不是将年轻代的这些对象都清空,而是有些升级到了老年代Tenured

​ 4、之后是本次收集的内存的终结及CPU用户态、系统态的使用时间。

第二次:

[0.904s][info][gc,start     ] GC(1) Pause Young (Allocation Failure)[0.951s][info][gc,heap      ] GC(1) DefNew: 39296K->4351K(39296K)[0.951s][info][gc,heap      ] GC(1) Tenured: 22930K->52762K(87424K)[0.951s][info][gc,metaspace ] GC(1) Metaspace: 5428K->5428K(1056768K)[0.951s][info][gc           ] GC(1) Pause Young (Allocation Failure) 60M->55M(123M) 46.776ms[0.951s][info][gc,cpu       ] GC(1) User=0.05s Sys=0.00s Real=0.05s

​ 可以看到第二次老年代进行增加。

第三次:

[1.402s][info][gc,start     ] GC(3) Pause Young (Allocation Failure)[1.402s][info][gc,start     ] GC(4) Pause Full (Allocation Failure)[1.402s][info][gc,phases,start] GC(4) Phase 1: Mark live objects[1.429s][info][gc,phases      ] GC(4) Phase 1: Mark live objects 26.826ms[1.429s][info][gc,phases,start] GC(4) Phase 2: Compute new object addresses[1.456s][info][gc,phases      ] GC(4) Phase 2: Compute new object addresses 27.384ms[1.456s][info][gc,phases,start] GC(4) Phase 3: Adjust pointers[1.475s][info][gc,phases      ] GC(4) Phase 3: Adjust pointers 18.904ms[1.475s][info][gc,phases,start] GC(4) Phase 4: Move objects[1.501s][info][gc,phases      ] GC(4) Phase 4: Move objects 26.365ms[1.501s][info][gc             ] GC(4) Pause Full (Allocation Failure) 102M->47M(123M) 99.674ms[1.501s][info][gc,heap        ] GC(3) DefNew: 39296K->0K(39296K)[1.501s][info][gc,heap        ] GC(3) Tenured: 65565K->48148K(87424K)[1.501s][info][gc,metaspace   ] GC(3) Metaspace: 5428K->5428K(1056768K)[1.501s][info][gc             ] GC(3) Pause Young (Allocation Failure) 102M->47M(123M) 99.732ms[1.501s][info][gc,cpu         ] GC(3) User=0.08s Sys=0.00s Real=0.10s

​ 我们看第三次的日志,与前面是不同的,关键是本次进行了Tenured老年代的回收,因为前面一次将年轻代的内容添加到了老年代,现在老年代也需要回收了:

​ 1、这里首先是Pause Young暂停回收年轻代,然后发现需要进行老年代的回收了,使用这里然后是有一个Pause Full暂停所有代,然后进行回收

​ 2、我们再看下老年代的收集的相关日志,由于Serial老年代使用的是MarkSweepCompact标记清除压缩算法:

​ 所以这里首先是Phase 1: Mark live objects:标记存活对象

​ 然后Compute new object addresses:计算这些存活的对象需要移到的新地址(为压缩做准备)

​ 之后Adjust pointers:移动这些存活对象指针,将其指向新的地址

​ 最后Phase 4: Move objects进行存活对象的移动。

3)、源码部分

class GenMarkSweep : public MarkSweep {  friend class VM_MarkSweep;  friend class G1MarkSweep; public:  static void invoke_at_safepoint(ReferenceProcessor* rp, bool clear_all_softrefs); private:  // Mark live objects  static void mark_sweep_phase1(bool clear_all_softrefs);  // Calculate new addresses  static void mark_sweep_phase2();  // Update pointers  static void mark_sweep_phase3();  // Move objects to new positions  static void mark_sweep_phase4();	........};

​ 可以看到这里定义了1个阶段的方法,然后还定义了一个invoke_at_safepoint方法,即在安全点执行垃圾回收。

void GenMarkSweep::invoke_at_safepoint(ReferenceProcessor* rp, bool clear_all_softrefs) {  assert(SafepointSynchronize::is_at_safepoint(), "must be at a safepoint");  GenCollectedHeap* gch = GenCollectedHeap::heap();	......  allocate_stacks();  mark_sweep_phase1(clear_all_softrefs);  mark_sweep_phase2();	.........  mark_sweep_phase3();  mark_sweep_phase4();  restore_marks();  // Set saved marks for allocation profiler (and other things? -- dld)  // (Should this be in general part?)  gch->save_marks();  deallocate_stacks();  // If compaction completely evacuated the young generation then we  // can clear the card table.  Otherwise, we must invalidate  // it (consider all cards dirty).  In the future, we might consider doing  // compaction within generations only, and doing card-table sliding.  CardTableRS* rs = gch->rem_set();  Generation* old_gen = gch->old_gen();	.......		.........  // Update heap occupancy information which is used as  // input to soft ref clearing policy at the next gc.  Universe::update_heap_info_at_gc();		.......}

然后关于这些步骤方法的具体逻辑,目前就不分析了(能力&时间有限,过一段时间再看,这些具体的实现分析起来应该还是很复杂的)

关于Serial垃圾收集器的使用&日志就分析到这里。

2、ParNew&CMS(并发标记清除垃圾回收器)

1)、基本介绍

ParNew:

ParNew垃圾回收器是回收年轻代的,其是以并行回收的方式进行垃圾回收,其与SerialDefNew的模式是类似的,使用复制算法&Stop-The-World的模式,只是将原来的单线程变为多线程。同时我们看Generation的定义:

class ParNewGeneration: public DefNewGeneration {

ParNewGeneration是继承的DefNewGeneration。并且这两种处理的堆都是GenCollectedHeap

在这里插入图片描述

CMS

ConcurrentMarkSweep(CMS)垃圾回收器是回收老年代的、其是标记-清除并发垃圾收集器,能跟用户线程一起运行(但也会有STW,同时最终也可能会使用SerialOld去收集,即会出现较长的STW),其特点是并发低延迟(强交互,也就是一次暂停较短的时间)。但其可能会出现本次没有回收的浮动逻辑,因为其在并发标记阶段可能又出现了新的垃圾,但其最后阶段的暂停标记只是确定在并发标记阶段的一些可能会回收的对象,例如可能会使用finalize()方法这些来复活一些对象等情况,但并不会再全都重新扫描。这些情况其实也就决定了其并不能在垃圾占满了整个空间空间再回收(可能会出现Concurrent Mode Failure),需要一个参数(CMSInitiatingOccupancyFraction)表示占满百分之多少就提前进行回收。

在这里插入图片描述

2)、使用&日志分析

我们使用-Xlog:gc* -Xmx128M -Xms128M -XX:+UseParNewGC这个参数的时候,是会报错的:

Java HotSpot(TM) 64-Bit Server VM warning: Option UseParNewGC was deprecated in version 9.0 and will likely be removed in a future release.It is not possible to combine the ParNew young collector with any collector other than CMS.Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.

​ 这里我们可以去看前面的check_gc_consistency()逻辑回收器的选择,因为UseParallelGCUseConcMarkSweepGC是一起配合使用 的,所以我们需要参数改为-Xlog:gc* -Xmx128M -Xms128M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC。但这里如果我们只是使用-XX:+UseConcMarkSweepGC,其会默认将年轻代设置为UseParNewGC,这个通过前面的ConcurrentMarkSweepPolicy::initialize_generations()梳理,其会将年轻代设置为Generation::ParNew。例如我们再使用-Xlog:gc* -Xmx128M -Xms128M -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal,通过PrintFlagsFinal打印的参数:

在这里插入图片描述

在这里插入图片描述

但如果我们修改下,通过参数-Xlog:gc* -Xmx128M -Xms128M -XX:-UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal,去掉UseParNewGC,其就会报:

It is not possible to combine the DefNew young collector with the CMS collector.Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.

下面我们来看下GC日志:

[0.028s][info][gc] Using Concurrent Mark Sweep[0.028s][info][gc,heap,coops] Heap address: 0x00000000f8000000, size: 128 MB, Compressed Oops mode: 32-bit

第一次回收:

[0.551s][info][gc,start     ] GC(0) Pause Young (Allocation Failure)[0.551s][info][gc,task      ] GC(0) Using 4 workers of 4 for evacuation[0.640s][info][gc,heap      ] GC(0) ParNew: 34944K->4352K(39296K)[0.640s][info][gc,heap      ] GC(0) CMS: 0K->22993K(87424K)[0.640s][info][gc,metaspace ] GC(0) Metaspace: 5425K->5425K(1056768K)[0.640s][info][gc           ] GC(0) Pause Young (Allocation Failure) 34M->26M(123M) 88.537ms[0.640s][info][gc,cpu       ] GC(0) User=0.27s Sys=0.03s Real=0.09s

​ 这里整体与前面的Serial是类似的,但一些词有变化,可以看到这里的年轻代表示是ParNew,而SerialDefNew,同时前老年代是用的Tenured表示,这里是CMS。这里是回收年轻代,过程是暂停回收年轻代,同时可以看到老年代CMS增加了。

第二次回收:

[0.807s][info][gc,start     ] GC(1) Pause Young (Allocation Failure)[0.807s][info][gc,task      ] GC(1) Using 4 workers of 4 for evacuation[0.839s][info][gc,heap      ] GC(1) ParNew: 39296K->4352K(39296K)[0.839s][info][gc,heap      ] GC(1) CMS: 22993K->52794K(87424K)[0.839s][info][gc,metaspace ] GC(1) Metaspace: 5426K->5426K(1056768K)[0.839s][info][gc           ] GC(1) Pause Young (Allocation Failure) 60M->55M(123M) 32.434ms[0.840s][info][gc,cpu       ] GC(1) User=0.16s Sys=0.02s Real=0.03s

​ 这里同样是是回收的年轻代

第三次回收:

[0.882s][info   ][gc,start     ] GC(2) Pause Initial Mark[0.882s][info   ][gc           ] GC(2) Pause Initial Mark 56M->56M(123M) 0.676ms[0.882s][info   ][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s[0.882s][info   ][gc           ] GC(2) Concurrent Mark[0.920s][info   ][gc           ] GC(2) Concurrent Mark 37.571ms[0.920s][info   ][gc,cpu       ] GC(2) User=0.06s Sys=0.00s Real=0.04s[0.920s][info   ][gc           ] GC(2) Concurrent Preclean[0.920s][info   ][gc           ] GC(2) Concurrent Preclean 0.146ms[0.920s][info   ][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s[0.920s][info   ][gc           ] GC(2) Concurrent Abortable Preclean[1.049s][info   ][gc,start     ] GC(3) Pause Young (Allocation Failure)[1.049s][info   ][gc,task      ] GC(3) Using 4 workers of 4 for evacuation[1.084s][info   ][gc,heap      ] GC(3) ParNew: 39296K->4352K(39296K)[1.084s][info   ][gc,heap      ] GC(3) CMS: 52798K->82636K(87424K)[1.084s][info   ][gc,metaspace ] GC(3) Metaspace: 5433K->5433K(1056768K)[1.084s][info   ][gc           ] GC(3) Pause Young (Allocation Failure) 89M->84M(123M) 34.800ms[1.084s][info   ][gc,cpu       ] GC(3) User=0.11s Sys=0.00s Real=0.04s[1.102s][info   ][gc           ] GC(2) Concurrent Abortable Preclean 182.344ms[1.102s][info   ][gc,cpu       ] GC(2) User=0.28s Sys=0.00s Real=0.18s[1.102s][info   ][gc,start     ] GC(2) Pause Remark[1.105s][info   ][gc           ] GC(2) Pause Remark 89M->89M(123M) 2.233ms[1.105s][info   ][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s[1.105s][info   ][gc           ] GC(2) Concurrent Sweep[1.132s][info   ][gc           ] GC(2) Concurrent Sweep 26.989ms[1.132s][info   ][gc,cpu       ] GC(2) User=0.06s Sys=0.00s Real=0.03s[1.132s][info   ][gc           ] GC(2) Concurrent Reset[1.132s][info   ][gc           ] GC(2) Concurrent Reset 0.055ms[1.132s][info   ][gc,cpu       ] GC(2) User=0.00s Sys=0.00s Real=0.00s[1.132s][info   ][gc,heap      ] GC(2) Old: 52798K->82635K(87424K)[1.233s][info   ][gc,start     ] GC(4) Pause Young (Allocation Failure)[1.233s][info   ][gc,task      ] GC(4) Using 4 workers of 4 for evacuation[1.233s][info   ][gc,start     ] GC(5) Pause Full (Allocation Failure)[1.233s][info   ][gc,phases,start] GC(5) Phase 1: Mark live objects[1.285s][info   ][gc,phases      ] GC(5) Phase 1: Mark live objects 51.888ms[1.285s][info   ][gc,phases,start] GC(5) Phase 2: Compute new object addresses[1.337s][info   ][gc,phases      ] GC(5) Phase 2: Compute new object addresses 51.621ms[1.337s][info   ][gc,phases,start] GC(5) Phase 3: Adjust pointers[1.392s][info   ][gc,phases      ] GC(5) Phase 3: Adjust pointers 55.136ms[1.392s][info   ][gc,phases,start] GC(5) Phase 4: Move objects[1.534s][info   ][gc,phases      ] GC(5) Phase 4: Move objects 141.695ms[1.534s][info   ][gc             ] GC(5) Pause Full (Allocation Failure) 119M->114M(123M) 300.734ms[1.534s][info   ][gc,heap        ] GC(4) ParNew: 39296K->29369K(39296K)[1.534s][info   ][gc,heap        ] GC(4) CMS: 82635K->87423K(87424K)[1.534s][info   ][gc,metaspace   ] GC(4) Metaspace: 5433K->5433K(1056768K)[1.534s][info   ][gc             ] GC(4) Pause Young (Allocation Failure) 119M->114M(123M) 300.811ms[1.534s][info   ][gc,cpu         ] GC(4) User=0.25s Sys=0.00s Real=0.30s

​ 这里就有使用CMS回收老年代。由于这里ParNewCMS都是使用的多线程处理,所以这里的日志是两种混合的,我们看的会很乱。这里我们主要看GC(2)GC(5),因为这两个是CMS的回收日志,而GC(3)GC(4)我们可以看到其明显有Pause Young,表明其实ParNew

​ 首先是Pause Initial Mark,这个就是初始化标记,同时进行了Pause即会暂停用户线程**(STW)**。

​ 然后是Concurrent Mark,并发标记,表示这个时候垃圾清理线程是与用户线程并发运行的。

​ 之后是Concurrent Preclean,并发前置清理。

​ 再进行Concurrent Abortable Preclean,是并发终止前置清理的意思?

​ 再Pause Remark,暂停标记,这里也就是会发生STWSTW)。

​ 前面完成标记后就进行Concurrent Sweep,并发标记清除了。

​ 最后进行Concurrent Reset,并发重置,就完成本次清理工作了。

​ 这里的Pause Initial Mark标记是标记GC Roots能直接关联的对象,速度会很快。Concurrent Mark是进行GC Roots Tracing过程。Pause Remark是修正并发标记是对象发生的一些变动,例如一些对象再这个时候可能复活等。

​ 但我们也可以看到GC(5)其是使用Serial的方式来清理的,所以其实CMS也是会使用SeialOld来进行清理的,这样就能实现压缩消除碎片了,但我们知道SerialOld是单线程的,所以如果CMS使用SerialOld的话,就会产生单线程的STW。

在这里插入图片描述

在这里插入图片描述

3)、源码分析

​ 这里源码我只要知道了CMS定义的回收过程的状态,即这些状态的逻辑流程:

// CMS abstract state machine // initial_state: Idling // next_state(Idling)            = {Marking} // next_state(Marking)           = {Precleaning, Sweeping} // next_state(Precleaning)       = {AbortablePreclean, FinalMarking} // next_state(AbortablePreclean) = {FinalMarking} // next_state(FinalMarking)      = {Sweeping} // next_state(Sweeping)          = {Resizing} // next_state(Resizing)          = {Resetting} // next_state(Resetting)         = {Idling} // The numeric values below are chosen so that: // . _collectorState <= Idling ==  post-sweep && pre-mark // . _collectorState in (Idling, Sweeping) == {initial,final}marking || //                                            precleaning || abortablePrecleanbpublic: enum CollectorState {   Resizing            = 0,   Resetting           = 1,   Idling              = 2,   InitialMarking      = 3,   Marking             = 4,   Precleaning         = 5,   AbortablePreclean   = 6,   FinalMarking        = 7,   Sweeping            = 8 };

​ 这里我们可以看到,最开始是Idling空闲状态 -> Marking再转换到标记阶段(可以是并发标记也可以STW标记)-> Precleaning/Sweeping 标记后可以转到前置清理或者清理垃圾。这里下面的就不具体讲叙了,最后回到Resetting重置状态,至此就完成整个清理状态了,然后又会回到Idling空闲状态。

void CMSCollector::collect_in_background(GCCause::Cause cause) {  assert(Thread::current()->is_ConcurrentGC_thread(),    "A CMS asynchronous collection is only allowed on a CMS thread.");  GenCollectedHeap* gch = GenCollectedHeap::heap();  {    bool safepoint_check = Mutex::_no_safepoint_check_flag;    MutexLockerEx hl(Heap_lock, safepoint_check);    FreelistLocker fll(this);    MutexLockerEx x(CGC_lock, safepoint_check);    	........    {      // Check if the FG collector wants us to yield.      CMSTokenSync x(true); // is cms thread      if (waitForForegroundGC()) {        // We yielded to a foreground GC, nothing more to be        // done this round.        	......        return;      } else {        // The background collector can run but check to see if the        // foreground collector has done a collection while the        // background collector was waiting to get the CGC_lock        // above.  If yes, break so that _foregroundGCShouldWait        // is cleared before returning.        if (_collectorState == Idling) {          break;        }      }    }    switch (_collectorState) {      case InitialMarking:        {          ReleaseForegroundGC x(this);          stats().record_cms_begin();          VM_CMS_Initial_Mark initial_mark_op(this);          VMThread::execute(&initial_mark_op);        }        // The collector state may be any legal state at this point        // since the background collector may have yielded to the        // foreground collector.        break;      case Marking:        // initial marking in checkpointRootsInitialWork has been completed        if (markFromRoots()) { // we were successful          assert(_collectorState == Precleaning, "Collector state should "            "have changed");        } else {          assert(_foregroundGCIsActive, "Internal state inconsistency");        }        break;      case Precleaning:        // marking from roots in markFromRoots has been completed        preclean();        break;      case AbortablePreclean:        abortable_preclean();        break;      case FinalMarking:        {          ReleaseForegroundGC x(this);          VM_CMS_Final_Remark final_remark_op(this);          VMThread::execute(&final_remark_op);        }        break;      case Sweeping:        // final marking in checkpointRootsFinal has been completed        sweep();   		...........      case Resizing: {        // Sweeping has been completed...        // At this point the background collection has completed.        // Don't move the call to compute_new_size() down        // into code that might be executed if the background        // collection was preempted.        .........        break;      }      case Resetting:        // CMS heap resizing has been completed       ........        break;      case Idling:      default:        ShouldNotReachHere();        break;    }  }	......}

​ 这里就是其中的部分流转,同时通过注释,也可以看到这些状态的流转是有顺序的,就如前面描叙状态流转一样。

​ 然后其使用SerialOld的流程:

void ConcurrentMarkSweepGeneration::collect(bool   full,                                            bool   clear_all_soft_refs,                                            size_t size,                                            bool   tlab){  collector()->collect(full, clear_all_soft_refs, size, tlab);}
void CMSCollector::collect(bool   full,                           bool   clear_all_soft_refs,                           size_t size,                           bool   tlab){  ........  acquire_control_and_collect(full, clear_all_soft_refs);}

​ 其最终会调到:

// A work method used by the foreground collector to do// a mark-sweep-compact.void CMSCollector::do_compaction_work(bool clear_all_soft_refs) {  GenCollectedHeap* gch = GenCollectedHeap::heap();  	.........  GenMarkSweep::invoke_at_safepoint(ref_processor(), clear_all_soft_refs);	.........}

​ 这个invoke_at_safepoint,前面有讲。

3、Parallel Scaveng 回收器

1)、基本介绍

Parallel Scaveng垃圾回收器是个对应具体的实现即UseParallelGC用于年轻代、UseParallelOldGC用于老年代。

​ 然后其基本的原理性的内容与前面的ParNew是大体相同的,其也是复制算法STW、然后其老年代是使用标记清除压缩算法。其收集过程是会STW,让所有用户线程停止,然后让多个垃圾回收线程并行处理。其的特点是注重吞吐量、然后能自动调节各个年龄代的空间大小,需要使用UseAdaptiveSizePolicy参数。

2)、使用&日志分析

​ 我们使用-Xlog:gc* -XX:+UseParallelGC -Xmx128M -Xms128M,然后其默认老年代使用的就是UseParallelOldGC

下面我们看其日志:

[0.030s][info][gc] Using Parallel[0.030s][info][gc,heap,coops] Heap address: 0x00000000f8000000, size: 128 MB, Compressed Oops mode: 32-bit

第一次GC日志:

[0.532s][info][gc,start     ] GC(0) Pause Young (Allocation Failure)[0.562s][info][gc,heap      ] GC(0) PSYoungGen: 33280K->5117K(38400K)[0.562s][info][gc,heap      ] GC(0) ParOldGen: 0K->20944K(87552K)[0.562s][info][gc,metaspace ] GC(0) Metaspace: 5426K->5426K(1056768K)[0.562s][info][gc           ] GC(0) Pause Young (Allocation Failure) 32M->25M(123M) 30.129ms[0.562s][info][gc,cpu       ] GC(0) User=0.06s Sys=0.00s Real=0.03s

​ 可以看到这里年轻代使用的名称是PSYoungGen,老年代使用的是ParOldGen。然后这里就是STW暂停年轻代通过并行回收年轻代,然后一些不能回收的转移到老年代

第二次GC日志

[0.788s][info][gc,start     ] GC(1) Pause Young (Allocation Failure)[0.838s][info][gc,heap      ] GC(1) PSYoungGen: 38397K->5118K(38400K)[0.838s][info][gc,heap      ] GC(1) ParOldGen: 20944K->49489K(87552K)[0.838s][info][gc,metaspace ] GC(1) Metaspace: 5427K->5427K(1056768K)[0.838s][info][gc           ] GC(1) Pause Young (Allocation Failure) 57M->53M(123M) 49.640ms[0.838s][info][gc,cpu       ] GC(1) User=0.11s Sys=0.00s Real=0.05s

​ 与第一次整体一样,下面我们直接到第4次,因为第3次也是年轻代。

第四次GC日志

[1.012s][info][gc,start     ] GC(3) Pause Full (Ergonomics)[1.012s][info][gc,phases,start] GC(3) Marking Phase[1.024s][info][gc,phases      ] GC(3) Marking Phase 12.267ms[1.024s][info][gc,phases,start] GC(3) Summary Phase[1.024s][info][gc,phases      ] GC(3) Summary Phase 0.008ms[1.024s][info][gc,phases,start] GC(3) Adjust Roots[1.026s][info][gc,phases      ] GC(3) Adjust Roots 1.549ms[1.026s][info][gc,phases,start] GC(3) Compaction Phase[1.042s][info][gc,phases      ] GC(3) Compaction Phase 16.251ms[1.042s][info][gc,phases,start] GC(3) Post Compact[1.043s][info][gc,phases      ] GC(3) Post Compact 0.945ms[1.043s][info][gc,heap        ] GC(3) PSYoungGen: 5109K->0K(38400K)[1.043s][info][gc,heap        ] GC(3) ParOldGen: 58652K->14191K(87552K)[1.043s][info][gc,metaspace   ] GC(3) Metaspace: 5427K->5427K(1056768K)[1.043s][info][gc             ] GC(3) Pause Full (Ergonomics) 62M->13M(123M) 31.237ms[1.043s][info][gc,cpu         ] GC(3) User=0.09s Sys=0.00s Real=0.03s

​ 可以看到这里首先是一个Pause Full表示STW堆空间。然后是其回收的几个步骤:

​ 首先是Marking Phase标记阶段 -> 然后是Summary Phase总结阶段 ->再适合Adjust Roots调节GC Roots

// Adjust addresses in roots.  Does not adjust addresses in heap.static void adjust_roots(ParCompactionManager* cm);

​ 这个阶段看注释应该是本次只是在GC Roots中改变这个对象将要指向的新地址,当这个对象还在原来的地址,然后是 -> Compaction Phase压缩阶段,这个阶段就会正式移动对象到新的地址了 -> 最后是一些后置压缩的处理。

3)、源码分析

// This method contains no policy. You should probably// be calling invoke() instead.bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { 	...........  ParallelScavengeHeap* heap = ParallelScavengeHeap::heap();	..........  // Make sure data structures are sane, make the heap parsable, and do other  // miscellaneous bookkeeping.  pre_compact();	....  // Get the compaction manager reserved for the VM thread.  ParCompactionManager* const vmthread_cm =    ParCompactionManager::manager_array(gc_task_manager()->workers());  {   	............    bool marked_for_unloading = false;    marking_start.update();    marking_phase(vmthread_cm, maximum_heap_compaction, &_gc_tracer);    bool max_on_system_gc = UseMaximumCompactionOnSystemGC      && GCCause::is_user_requested_gc(gc_cause);    summary_phase(vmthread_cm, maximum_heap_compaction || max_on_system_gc);    // adjust_roots() updates Universe::_intArrayKlassObj which is    // needed by the compaction for filling holes in the dense prefix.    adjust_roots(vmthread_cm);    compaction_start.update();    compact();    // Reset the mark bitmap, summary data, and do other bookkeeping.  Must be    // done before resizing.    post_compact();  	...........  return true;}

​ 这里的回收过程就在这里,我们目前就了解下这些简单流程。

4、G1回收器

1、基本介绍

​ G1也是一个并行垃圾回收器,它是在堆的基础上再进行分区(Region)。通过将堆划分成更小的Region。用很多的Region来表示EdenSurvivor 0Survivor 1老年代这些,这些表示不同代的Region是可以不连续的。其特点是垃圾优先,同时由于其分成很多小的Region,所以其可以很好的控制每次的收集暂停时间。

​ G1收集是在并发全局标记的时候,确定哪些Region大部分是垃圾,在回收的时候就优先回收这些Region,也就是前面提到的垃圾优先,然后将在这些Region残留的存活对象再copy到一个Region中(能放的下的情况),所以其也就能更好的控制碎片。其也就解决了CMS不能压缩的问题,但其也是会产生浮动垃圾的,同时跟好的处理了使用单一的复制压缩算法SerialOld需要的长暂停问题。但要发挥其的长处,应该是那些用到大堆内存的应用。因为如果堆很小,但设计的这样复制,其是损耗是更大的。

在这里插入图片描述

​ 上面的H是表示的大区,因为可能有些对象一个Region放不下。

2、使用&日志

​ 其的使用就是-Xlog:gc* -XX:+UseG1GC -Xmx128M -Xms128M,其年轻代&老年代是配套的使用的。不需要针对不同的代进行选择,就是使用G1完成整个堆的收集。

[0.058s][info][gc,heap] Heap region size: 1M[0.066s][info][gc     ] Using G1[0.066s][info][gc,heap,coops] Heap address: 0x00000000f8000000, size: 128 MB, Compressed Oops mode: 32-bit

​ 可以看到这里有Heap region size: 1M,其就是表示每个Region的大小。

[0.843s][info][gc,start     ] GC(0) Pause Young (G1 Evacuation Pause)[0.843s][info][gc,task      ] GC(0) Using 4 workers of 4 for evacuation[0.851s][info][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms[0.851s][info][gc,phases    ] GC(0)   Evacuate Collection Set: 8.5ms[0.851s][info][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.2ms[0.851s][info][gc,phases    ] GC(0)   Other: 0.2ms[0.851s][info][gc,heap      ] GC(0) Eden regions: 14->0(8)[0.851s][info][gc,heap      ] GC(0) Survivor regions: 0->2(2)[0.851s][info][gc,heap      ] GC(0) Old regions: 0->9[0.851s][info][gc,heap      ] GC(0) Humongous regions: 0->0[0.852s][info][gc,metaspace ] GC(0) Metaspace: 5426K->5426K(1056768K)[0.852s][info][gc           ] GC(0) Pause Young (G1 Evacuation Pause) 14M->10M(128M) 8.838ms[0.852s][info][gc,cpu       ] GC(0) User=0.03s Sys=0.03s Real=0.01s

​ 整个就是暂停(STW)回收年轻代。这里需要注意的是Humongous regions: 0->0,其表示Region个数的变化,我们目前是没有大Region的,然后其他的阶段也是较清晰的。

[1.825s][info][gc,marking   ] GC(6) Concurrent Clear Claimed Marks[1.825s][info][gc,marking   ] GC(6) Concurrent Clear Claimed Marks 0.010ms[1.825s][info][gc,marking   ] GC(6) Concurrent Scan Root Regions[1.826s][info][gc,marking   ] GC(6) Concurrent Scan Root Regions 1.341ms[1.826s][info][gc,marking   ] GC(6) Concurrent Mark (1.826s)[1.826s][info][gc,marking   ] GC(6) Concurrent Mark From Roots[1.826s][info][gc,task      ] GC(6) Using 1 workers of 1 for marking[1.838s][info][gc,marking   ] GC(6) Concurrent Mark From Roots 11.709ms[1.838s][info][gc,marking   ] GC(6) Concurrent Mark (1.826s, 1.838s) 11.738ms[1.838s][info][gc,start     ] GC(6) Pause Remark[1.839s][info][gc,stringtable] GC(6) Cleaned string and symbol table, strings: 3388 processed, 15 removed, symbols: 24304 processed, 0 removed[1.839s][info][gc            ] GC(6) Pause Remark 76M->76M(128M) 0.958ms[1.839s][info][gc,cpu        ] GC(6) User=0.00s Sys=0.00s Real=0.00s[1.839s][info][gc,marking    ] GC(6) Concurrent Create Live Data[1.842s][info][gc,marking    ] GC(6) Concurrent Create Live Data 3.074ms[1.843s][info][gc,start      ] GC(6) Pause Cleanup[1.843s][info][gc            ] GC(6) Pause Cleanup 77M->17M(128M) 0.157ms[1.843s][info][gc,cpu        ] GC(6) User=0.00s Sys=0.00s Real=0.00s[1.843s][info][gc,marking    ] GC(6) Concurrent Complete Cleanup[1.843s][info][gc,marking    ] GC(6) Concurrent Complete Cleanup 0.032ms[1.843s][info][gc,marking    ] GC(6) Concurrent Cleanup for Next Mark[1.843s][info][gc,marking    ] GC(6) Concurrent Cleanup for Next Mark 0.124ms[1.843s][info][gc            ] GC(6) Concurrent Cycle 18.009ms

​ 这里目前还没有分析源码的过程,但可以看下官方文档,对其的描叙大体阶段是:

​ 1)、Initial marking phase: 初始化标记年轻代,这个阶段也是像前面的CMS那样,标记Root Region。这里是会STW的。

​ 2)、Root region scanning phase:这个的意思应该是,通过老年代去标记在初始化标记时Survivor区存活的对象,这个是并发的,不会STW

​ 3)、Concurrent marking phase

​ 3)、Concurrent marking phase:这个是并发标记整个堆中存活的对象。其可以被年轻代的STW中断。

​ 4)、Remark phase:STW重新标记。

​ 5)、Cleanup phase:最终的清理阶段,开始是STW的,但在最后,将空区域设置我空闲列表的时候其是部分并发的。

上一篇:JVM篇-常用控制参数使用分析
下一篇:JVM篇-结合源码分析垃圾收集器的类型

发表评论

最新留言

表示我来过!
[***.240.166.169]2025年04月09日 23时42分37秒