深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略

Mixed GC

Mixed GC(混合回收)是G1独有的回收策略,它与YGC的回收策略的区别如下:

YGC:选定所有Eden Region放入CSet,使用多线程复制算法将

CSet的存活对象复制到Survivor Region或者晋升到Old Region。

Mixed GC:选定所有Eden Region和全局并发标记计算得到的收益较高的部分Old Region放入CSet,使用多线程复制算法将CSet的存活对象复制到Survivor Region或者晋升到Old Region。

深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略

相比于YGC,Mixed GC增加了全局并发标记过程,它能够回收部分Old Region,不会再出现在CMS GC中老年代出现的碎片化问题,因为当老年代被加入CSet后,G1会使用和YGC一样的复制算法整理空间。Mixed GC的回收过程如图11-4所示。

深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略

图11-4 Mixed GC的回收过程

总结来说,Mixed GC回收周期包括全局并发标记和复制存活对象,其中复制存活对象这一步完全复用YGC代码。

SATB

谈并发垃圾回收算法必然绕不过对象丢失问题。上节提到过,在并发标记过程中,只要满足两个条件就会造成对象丢失,解决方案通常有增量更新技术和SATB。CMS GC使用增量更新技术破坏第一个条件来解决对象丢失问题,而G1使用SATB破坏第二个条件来解决对象丢失问题。

SATB会在GC开始时为对象关系打下一个快照,除了快照中存活的对象和GC过程中分配的新对象外,其他都是死亡对象。SATB的快照和GC期间新分配的对象由TAMS实现。TAMS(Top At Mark Start)是一个指针,每个Region包括Bottom、End、Top、PrevTAMS和NextTAMS指针,如图11-5所示。

深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略

图11-5 Region的Bottom、End、Top、PrevTAMS和NextTAMS指针

当第N-1次并发标记开始时将Region的Top指针赋值给NextTAMS,标记期间如果Mutator线程分配新对象则移动Top指针,最终标记期间所有新分配的对象都位于NextTAMS与Top之间,SATB会将这些对象都标记为存活对象。并发标记完成后会将PrevTAMS指针移动到NextTAMS,重置NextTAMS与Bottom。第N次并发标记开始时将Bottom到PrevTAMS之间的存活对象当作快照,此时快照中的存活对象在第N次并发标记结束后也应该保持存活。

TAMS为并发标记创建了快照,便于后续进行新对象分配,但是它不能解决并发标记期间Mutator线程造成的对象丢失问题(见10.5.2节),这样会破坏快照的完整性,为此G1使用SATB写屏障捕获所有对象修改,如代码清单11-5所示:

代码清单11-5 SATB写屏障

JRT_LEAF(void,G1BarrierSetRuntime::write_ref_field_pre_entry(..))
G1ThreadLocalData::satb_mark_queue(thread).enqueue(orig);
JRT_END
class STABMarkQueue: public PrtQueue {
void enqueue(void* ptr) {
// 如果不是并发标记阶段,SATB写屏障不会将对象记录到SATBMarkQueue
if (!_active) return; // _active只在并发标记阶段才为true
else enqueue_known_active(ptr);
}
...
};

SATB写屏障只是将所有引用关系发生修改的对象放入线程独有的SATBMarkQueue中。在SATBMarkQueue内部维护了一个buffer,如果buffer满了则将当前SATBMarkQueue放入一个全局的SATBMarkQueueSet中,然后为当前SATBMarkQueue重新分配buffer。

根据算法的实现原理,这些放入SATBMarkQueue的对象应该被标记,但SATB写屏障代码没有这么做,这是为了减小写屏障对Mutator线程性能的影响,SATB写屏障只是简单做记录,实际的标记工作是在全局并发标记过程中完成的。

全局并发标记

全局并发标记一般分为5步,具体内容如下。

1. 初始标记

初始标记是全局并发标记的第一阶段,是一个STW的过程,这一步会扫描GC Root直接可达的对象,并把它们复制到Survivor Region,该过程与YGC高度一致,所以G1完全复用了YGC的代码。也就是说,
do_collection_pause_at_safepoint()可以同时完成YGC和Mixed GC全局并发标记第一阶段这两件事情。


do_collection_pause_at_safepoint()如果发现这次回收策略是MixedGC,则会在G1ParTask的执行前后分别调用G1ConcurrentMark::pre_initial_mark和G1ConcurrentMark::post_initial_mark。前者将NextTAMS设置为top指针,为并发标记快照做好准备;后者通知根Region扫描准备就绪。


do_collection_pause_at_safepoint()会在最后将全局并发标记设置为started。

2. 根Region扫描

全局并发标记的大部分过程由G1ConcurrentMarkThread在后台完成,如代码清单11-6所示:代码清单11-6
G1ConcurrentMarkThread::run_service

void G1ConcurrentMarkThread::run_service() {
...
while (!should_terminate()) {
// 睡眠,直到条件满足,醒来后进行全局并发标记
sleep_before_next_cycle();
if (should_terminate()) {
break;
}
// 根Region扫描
_cm->scan_root_regions();
for (uint iter = 1; !_cm->has_aborted(); ++iter) {
// 并发标记
_cm->mark_from_roots();
if (_cm->has_aborted()) break;
...
if (_cm->has_aborted()) break;
// STW重新标记
CMRemark cl(_cm);
VM_G1Concurrent op(&cl, "Pause Remark");
VMThread::execute(&op);
if (_cm->has_aborted()) break;
...
}
...// STW清理
if (!_cm->has_aborted()) {
CMCleanup cl_cl(_cm);
VM_G1Concurrent op(&cl_cl, "Pause Cleanup");
VMThread::execute(&op);
}
...
}
}

sleep_before_next_cycle()只会在条件满足时醒来,而条件就是started标志,该标记会在第一阶段初始标记结束时设置,所以初始标记后紧接着G1ConcurrentMark线程就会醒来进行全局并发标记。

作为全局并发标记的第二阶段,根Region扫描(scan_root_regions())是一个并发过程,它将G1CMRootRegionScanTask投递给线程执行。第一阶段初始标记完成后,Eden Region存活对象已经被晋升或者复制到Survivor Region。根Region扫描将会以Survivor Region中的存活对象作为根,扫描被它们引用的对象,并在NextBitmap标记。

3. 并发标记

全局并发标记的第三阶段是并发标记,并发标记期间可以发生很多次YGC。G1投递G1CMConcurrentMarkingTask任务等待线程组执行,该任务又会进一步调用G1CMTask::do_marking_step,这是并发标记的实际实现。

在并发标记期间,Mutator线程可以修改对象引用,这些被修改的对象会被放入SATBMarkQueue,do_marking_step找到位于SATBMarkQueue的对象后将它们标记为灰色,这样可以让这些对象被进一步扫描,从而解决引用修改导致的对象丢失问题。

 

另外,由于根Region扫描阶段只是将Survivor Region的存活对象当作根处理,这些对象的成员仍然是白色对象,所以do_marking_step也会处理它们。

4. 重新标记

重新标记与并发标记非常相似,事实上它们共用代码:重新标记投递G1CMRemarkTask给线程组执行。G1CMRemarkTask也是调用G1CMTask::do_marking_step完成的。

do_marking_step有一个参数time_target_ms,表示目标清理时间,如果超过这个时间,do_marking_step会终止执行。初始标记传递给do_maring_step的目标清理时间是10ms(由参数
-XX:G1ConcMarkStepDurationMillis指定),表示并发标记在10ms内完成,超过时间会中断,而重新标记传递的目标清理时间约11天(1000000000.0ms),表示无论如何,重新标记都会完成。

GC如果想要结束标记阶段,需要满足两个条件:第一个是处理完Survivor Region的所有存活对象和它们的成员,第二个是处理完位于SATBMarkQueue中的所有引用更改产生的灰色对象及其成员。由于标记前打过快照,Survivor Region的存活对象不会增长,所以第一个条件很容易满足,但是如果没有一个STW步骤,第二个条件将永远无法满足,因为在Mutator线程和GC线程并发期间,Mutator线程可能会不断修改对象引用,所以需要重新标记:重新标记是一个STW过程,在这个过程中Mutator线程停止,GC线程处理SATBMarkQueue中剩余的对象。

5. 清理

全局并发标记的最后一步是清理,这也是一个STW过程。G1投递VM_G1Concurrent给线程组,这是一个VM_Operation,最终会被VMThread消费并促使所有线程到达安全点,然后G1投递CMCleanup任务给线程组,该任务最终调用G1ConcurrentMark::cleanup进行清理工作。cleanup会重置一些数据结构,如果发现Humongous Region有很大的RSet则会将其清理掉。

至此,并发标记的工作就完成了,cleanup调用
record_concurrent_mark_cleanup_end告知下一次YGC需要清理新生代和部分老年代,还会设置CollectionSetChooser,选择老年代中存活对象较少的Region,为后期YGC的CSet选择做好准备。

 对象复制

对象复制过程又叫作Evacuation,是一个STW过程。这一步会复用YGC的代码,只是正常YGC的CSet只选择Young Region,而Mixed GC复用YGC代码,在创建CSet时会选择所有Young Region和部分收益较高的Old Region,将CSet中的存活对象复制到Survivor Region,然后回收原来的Region空间。

本文给大家讲解的内容是深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略

  1. 下篇文章给大家讲解的是深入解析java虚拟机:Full GC和字符串去重;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
上一篇:JVM垃圾收集器专题


下一篇:国外大神建立了一个深度神经网络来玩FIFA 18