重庆哪里有做网站的公司,wordpress 资源站主题,建设旅游信息网站的好处,wamp网站建设转载自 可能是最全面的 Java G1学习笔记
引子
最近遇到很多朋友过来咨询G1调优的问题#xff0c;我自己去年有专门学过一次G1#xff0c;但是当时只是看了个皮毛#xff0c;因此自己也有不少问题。总体来讲#xff0c;对于G1我有几个疑惑#xff0c;希望能够在这篇文章中…转载自 可能是最全面的 Java G1学习笔记
引子
最近遇到很多朋友过来咨询G1调优的问题我自己去年有专门学过一次G1但是当时只是看了个皮毛因此自己也有不少问题。总体来讲对于G1我有几个疑惑希望能够在这篇文章中得到解决。 G1出现的初衷是什么 G1适合在什么场景下使用 G1的trade-off是什么 G1的详细过程 如何理解G1的gc日志? G1的调优思路 G1和CMS的对比和选择
一、基础知识
1. 初衷
在G1提出之前经典的垃圾收集器主要有三种类型串行收集器、并行收集器和并发标记清除收集器这三种收集器分别可以是满足Java应用三种不同的需求内存占用及并发开销最小化、应用吞吐量最大化和应用GC暂停时间最小化但是上述三种垃圾收集器都有几个共同的问题1所有针对老年代的操作必须扫描整个老年代空间2新生代和老年代是独立的连续的内存块必须先决定年轻代和老年代在虚拟地址空间的位置。
2. 设计目标
G1是一种服务端应用使用的垃圾收集器目标是用在多核、大内存的机器上它在大多数情况下可以实现指定的GC暂停时间同时还能保持较高的吞吐量。
3. 使用场景
G1适用于以下几种应用 可以像CMS收集器一样允许垃圾收集线程和应用线程并行执行即需要额外的CPU资源 压缩空闲空间不会延长GC的暂停时间 需要更易预测的GC暂停时间 不需要实现很高的吞吐量
二、G1的重要概念
1. 分区Region
G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控制等问题——G1将整个堆分成相同大小的分区Region如下图所示。 G1的堆模型
每个分区都可能是年轻代也可能是老年代但是在同一时刻只能属于某个代。 年轻代、幸存区、老年代这些概念还存在成为逻辑上的概念这样方便复用之前分代框架的逻辑。在物理上不需要连续则带来了额外的好处——有的分区内垃圾对象特别多有的分区内垃圾对象很少G1会优先回收垃圾对象特别多的分区这样可以花费较少的时间来回收这些分区的垃圾这也就是G1名字的由来即首先收集垃圾最多的分区。
新生代其实并不是适用于这种算法的依然是在新生代满了的时候对整个新生代进行回收——整个新生代中的对象要么被回收、要么晋升至于新生代也采取分区机制的原因则是因为这样跟老年代的策略统一方便调整代的大小。
G1还是一种带压缩的收集器在回收老年代的分区时是将存活的对象从一个分区拷贝到另一个可用分区这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等但是都是2的冥次方。
2. 收集集合CSet
一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区CSet中的分区可以来自Eden空间、survivor空间、或者老年代。CSet会占用不到整个堆空间的1%大小。
3. 已记忆集合RSet
RSet记录了其他Region中的对象引用本Region中对象的关系属于points-into结构谁引用了我的对象。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象只需要扫描RSet即可。
如下图所示Region1和Region3中的对象都引用了Region2中的对象因此在Region2的RSet中记录了这两个引用。 RSet的示意图 摘一段R大的解释G1 GC则是在points-out的card table之上再加了一层结构来构成points-into RSet每个region会记录下到底哪些别的region有指向自己的指针而这些指针分别在哪些card的范围内。 这个RSet其实是一个hash tablekey是别的region的起始地址value是一个集合里面的元素是card table的index。 举例来说如果region A的RSet里有一项的key是region Bvalue里有index为1234的card它的意思就是region B的一个card里有引用指向region A。所以对region A来说该RSet记录的是points-into的关系而card table仍然记录了points-out的关系。 4. Snapshot-At-The-Beginning(SATB)
SATB是维持并发GC的正确性的一个手段G1GC的并发理论基础就是SATBSATB是由Taiichi Yuasa为增量式标记清除垃圾收集器设计的一个标记算法。Yuasa的SATAB的标记优化主要针对标记-清除垃圾收集器的并发标记阶段。按照R大的说法CMS的incremental update设计使得它在remark阶段必须重新扫描所有线程栈和整个young gen作为rootG1的SATB设计在remark阶段则只需要扫描剩下的satb_mark_queue。
SATB算法创建了一个对象图它是堆的一个逻辑“快照”。标记数据结构包括了两个位图previous位图和next位图。previous位图保存了最近一次完成的标记信息并发标记周期会创建并更新next位图随着时间的推移previous位图会越来越过时最终在并发标记周期结束的时候next位图会将previous位图覆盖掉。 下面我们以几个图例来描述SATB算法的过程
在并发周期开始之前NTAMS字段被设置到每个分区当前的顶部并发周期启动后分配的对象会被放在TAMS之前图里下边的部分同时被明确定义为隐式存活对象而TAMS之后图里上边的部分的对象则需要被明确地标记。 初始标记过程中的一个堆分区
并发标记过程中的堆分区 并发标记过程中的对分区
位于堆分区的Bottom和PTAMS之间的对象都会被标记并记录在previous位图中 位于Bottom和PTAMS之间的对象都会被标记在previous位图中
位于堆分区的Top和PATMS之间的对象均为隐式存活对象同时也记录在previous位图中 隐式存活标记是一种增量标记
在重新标记阶段的最后所有NTAMS之前的对象都会被标记 重新标记
在并发标记阶段分配的对象会被分配到NTAMS之后的空间它们会作为隐式存活对象被记录在next位图中。一次并发标记周期完成后这个next位图会覆盖previous位图然后将next位图清空。 开始并发标记后的对象会被识别为隐式存活对象放在next位图中
SATB是一个快照标记算法在并发标记进行的过程中垃圾收集器Collecotr和应用程序Mutator都在活动如果一个对象还没被mark到这时候Mutator就修改了它的引用那么这时候拿到的快照就是不完整的了如何解决这个问题呢?
G1 GC使用了SATB write barrier来解决这个问题——在并发标记过程中将该对象的旧的引用记录在一个SATB日志对列或缓冲区中。去翻G1的代码却发现实际代码如下——只该对象入队列并没有将整个修改过程放在写屏障之间完成。 // hotspot/src/share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.hpp// This notes that we dont need to access any BarrierSet data// structures, so this can be called from a static context.template class T static void write_ref_field_pre_static(T* field, oop newVal) {T heap_oop oopDesc::load_heap_oop(field);if (!oopDesc::is_null(heap_oop)) {enqueue(oopDesc::decode_heap_oop(heap_oop));}}enqueue的真正代码在hotspot/src/share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.cpp中这里使用JavaThread::satb_mark_queue_set().is_active()判断是否处于并发标记周期。
void G1SATBCardTableModRefBS::enqueue(oop pre_val) {// Nulls should have been already filtered.assert(pre_val-is_oop(true), Error);if (!JavaThread::satb_mark_queue_set().is_active()) return;Thread* thr Thread::current();if (thr-is_Java_thread()) {JavaThread* jt (JavaThread*)thr;//将旧值入队jt-satb_mark_queue().enqueue(pre_val);} else {MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag);JavaThread::satb_mark_queue_set().shared_satb_queue()-enqueue(pre_val);}
}stab_mark_queue.enqueue方法首先尝试将以前的值记录在一个缓冲区中如果这个缓冲区已经满了就会将当期这个SATB缓冲区“退休”并放入全局列表中然后再给线程分配一个新的SATB缓冲区。并发标记线程会定期检查和处理那些“被填满”的缓冲区。
三、G1的过程
1. 四个操作
G1收集器的收集活动主要有四种操作 新生代垃圾收集 后台收集、并发周期 混合式垃圾收集 必要时候的Full GC
第一、新生代垃圾收集的图例如下 Eden区耗尽的时候就会触发新生代收集新生代垃圾收集会对整个新生代进行回收 新生代垃圾收集期间整个应用STW 新生代垃圾收集是由多线程并发执行的 新生代收集结束后依然存活的对象会被拷贝到一个新的Survivor分区或者是老年代。
G1设计了一个标记阈值它描述的是总体Java堆大小的百分比默认值是45这个值可以通过命令-XX:InitiatingHeapOccupancyPercent(IHOP)来调整一旦达到这个阈值就回触发一次并发收集周期。注意这里的百分比是针对整个堆大小的百分比而CMS中的CMSInitiatingOccupancyFraction命令选型是针对老年代的百分比。并发收集周期的图例如下 在上图中有几个情况需要注意
1、新生代的空间占用情况发生了变化——在并发收集周期中至少有一次很可能是多次新生代垃圾收集
2、注意到一些分区被标记为X这些分区属于老年代它们就是标记周期找出的包含最多垃圾的分区注意它们内部仍然保留着数据
3、老年代的空间占用在标记周期结束后变得更多这是因为在标记周期期间新生代的垃圾收集会晋升对象到老年代而且标记周期中并不会是否老年代的任何对象。
第二、G1的并发标记周期包括多个阶段 并发标记周期采用的算法是我们前文提到的SATB标记算法产出是找出一些垃圾对象最多的老年代分区。
1、初始标记initial-mark在这个阶段应用会经历STW通常初始标记阶段会跟一次新生代收集一起进行换句话说——既然这两个阶段都需要暂停应用G1 GC就重用了新生代收集来完成初始标记的工作。 在新生代垃圾收集中进行初始标记的工作会让停顿时间稍微长一点并且会增加CPU的开销。初始标记做的工作是设置两个TAMS变量NTAMS和PTAMS的值所有在TAMS之上的对象在这个并发周期内会被识别为隐式存活对象 2、根分区扫描root-region-scan这个过程不需要暂停应用在初始标记或新生代收集中被拷贝到survivor分区的对象都需要被看做是根这个阶段G1开始扫描survivor分区所有被survivor分区所引用的对象都会被扫描到并将被标记。 survivor分区就是根分区正因为这个该阶段不能发生新生代收集如果扫描根分区时新生代的空间恰好用尽新生代垃圾收集必须等待根分区扫描结束才能完成。如果在日志中发现根分区扫描和新生代收集的日志交替出现就说明当前应用需要调优。 3、并发标记阶段concurrent-mark并发标记阶段是多线程的我们可以通过-XX:ConcGCThreads来设置并发线程数默认情况下G1垃圾收集器会将这个线程总数设置为并行垃圾线程数-XX:ParallelGCThreads的四分之一并发标记会利用trace算法找到所有活着的对象并记录在一个bitmap中因为在TAMS之上的对象都被视为隐式存活因此我们只需要遍历那些在TAMS之下的 记录在标记的时候发生的引用改变SATB的思路是在开始的时候设置一个快照然后假定这个快照不改变根据这个快照去进行trace这时候如果某个对象的引用发生变化就需要通过pre-write barrier logs将该对象的旧的值记录在一个SATB缓冲区中如果这个缓冲区满了就把它加到一个全局的列表中——G1会有并发标记的线程定期去处理这个全局列表。 4、重新标记阶段remarking重新标记阶段是最后一个标记阶段需要暂停整个应用G1垃圾收集器会处理掉剩下的SATB日志缓冲区和所有更新的引用同时G1垃圾收集器还会找出所有未被标记的存活对象。这个阶段还会负责引用处理等工作。 5、清理阶段cleanup清理阶段真正回收的内存很小截止到这个阶段,G1垃圾收集器主要是标记处哪些老年代分区可以回收将老年代按照它们的存活度liveness从小到大排列。 这个过程还会做几个事情识别出所有空闲的分区、RSet梳理、将不用的类从metaspace中卸载、回收巨型对象等等。识别出每个分区里存活的对象有个好处是在遇到一个完全空闲的分区时它的RSet可以立即被清理同时这个分区可以立刻被回收并释放到空闲队列中而不需要再放入CSet等待混合收集阶段回收梳理RSet有助于发现无用的引用。
第三、混合收集只会回收一部分老年代分区下图是第一次混合收集前后的堆情况对比。 混合收集会执行多次一直运行到几乎所有标记点老年代分区都被回收在这之后就会恢复到常规的新生代垃圾收集周期。当整个堆的使用率超过指定的百分比时G1 GC会启动新一轮的并发标记周期。在混合收集周期中对于要回收的分区会将该分区中存活的数据拷贝到另一个分区这也是为什么G1收集器最终出现碎片化的频率比CMS收集器小得多的原因——以这种方式回收对象实际上伴随着针对当前分区的压缩。
2. 两个模式
G1收集器的模式主要有两种 Young GC新生代垃圾收集 Mixed GC混合垃圾收集
在R大的帖子中给出了一个假象的G1垃圾收集运行过程如下图所示在结合上一小节的细节就可以将G1 GC的正常过程理解清楚了。 3. 巨型对象的管理
巨型对象在G1中如果一个对象的大小超过分区大小的一半该对象就被定义为巨型对象Humongous Object。巨型对象时直接分配到老年代分区如果一个对象的大小超过一个分区的大小那么会直接在老年代分配两个连续的分区来存放该巨型对象。巨型分区一定是连续的分配之后也不会被移动——没啥益处。
由于巨型对象的存在G1的堆中的分区就分成了三种类型新生代分区、老年代分区和巨型分区如下图所示 如果一个巨型对象跨越两个分区开始的那个分区被称为“开始巨型”后面的分区被称为“连续巨型”这样最后一个分区的一部分空间是被浪费掉的如果有很多巨型对象都刚好比分区大小多一点就会造成很多空间的浪费从而导致堆的碎片化。如果你发现有很多由于巨型对象分配引起的连续的并发周期并且堆已经碎片化明明空间够但是触发了FULL GC可以考虑调整-XX:G1HeapRegionSize参数减少或消除巨型对象的分配。
关于巨型对象的回收在JDK8u40之前巨型对象的回收只能在并发收集周期的清除阶段或FULL GC过程中过程中被回收在JDK8u40包括这个版本之后一旦没有任何其他对象引用巨型对象那么巨型对象也可以在年轻代收集中被回收。
4. G1执行过程中的异常情况
并发标记周期开始后的FULL GC
G1启动了标记周期但是在并发标记完成之前就发生了Full GC日志常常如下所示
51.408: [GC concurrent-mark-start]
65.473: [Full GC 4095M-1395M(4096M), 6.1963770 secs][Times: user7.87 sys0.00, real6.20 secs]
71.669: [GC concurrent-mark-abort]GC concurrent-mark-start开始之后就发生了FULL GC这说明针对老年代分区的回收速度比较慢或者说对象过快得从新生代晋升到老年代或者说是有很多大对象直接在老年代分配。针对上述原因我们可能需要做的调整有调大整个堆的大小、更快得触发并发回收周期、让更多的回收线程参与到垃圾收集的动作中。
混合收集模式中的FULL GC
在GC日志中观察到在一次混合收集之后跟着一条FULL GC这意味着混合收集的速度太慢在老年代释放出足够多的分区之前应用程序就来请求比当前剩余可分配空间大的内存。针对这种情况我们可以做的调整增加每次混合收集收集掉的老年代分区个数增加并发标记的线程数提高混合收集发生的频率。
疏散失败转移失败
在新生代垃圾收集快结束时找不到可用的分区接收存活下来的对象常见如下的日志
60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]这意味着整个堆的碎片化已经非常严重了我们可以从以下几个方面调整1增加整个堆的大小——通过增加-XX:G1ReservePercent选项的值并相应增加总的堆大小为“目标空间”增加预留内存量;2通过减少 -XX:InitiatingHeapOccupancyPercent提前启动标记周期3 你也可以通过增加-XX:ConcGCThreads选项的值来增加并发标记线程的数目
巨型对象分配失败
如果在GC日志中看到莫名其妙的FULL GC日志又对应不到上述讲过的几种情况那么就可以怀疑是巨型对象分配导致的这里我们可以考虑使用jmap命令进行堆dump然后通过MAT对堆转储文件进行分析。关于堆转储文件的分析技巧后续会有专门的文章介绍。
四、G1的调优
G1的调优目标主要是在避免FULL GC和疏散失败的前提下尽量实现较短的停顿时间和较高的吞吐量。关于G1 GC的调优需要记住以下几点
1、不要自己显式设置新生代的大小用Xmn或-XX:NewRatio参数如果显式设置新生代的大小会导致目标时间这个参数失效。 2、由于G1收集器自身已经有一套预测和调整机制了因此我们首先的选择是相信它即调整-XX:MaxGCPauseMillisN参数这也符合G1的目的——让GC调优尽量简单这里有个取舍如果减小这个参数的值就意味着会调小新生代的大小也会导致新生代GC发生得更频繁同时还会导致混合收集周期中回收的老年代分区减少从而增加FULL GC的风险。这个时间设置得越短应用的吞吐量也会受到影响。 3、针对混合垃圾收集的调优。如果调整这期望的最大暂停时间这个参数还是无法解决问题即在日志中仍然可以看到FULL GC的现象那么就需要自己手动做一些调整可以做的调整包括 1调整G1垃圾收集的后台线程数通过设置-XX:ConcGCThreadsn这个参数可以增加后台标记线程的数量帮G1赢得这场你追我赶的游戏 2调整G1垃圾收集器并发周期的频率如果让G1更早得启动垃圾收集也可以帮助G1赢得这场比赛那么可以通过设置-XX:InitiatingHeapOccupancyPercent这个参数来实现这个目标如果将这个参数调小G1就会更早得触发并发垃圾收集周期。 这个值需要谨慎设置如果这个参数设置得太高会导致FULL GC出现得频繁如果这个值设置得过小又会导致G1频繁得进行并发收集白白浪费CPU资源。通过GC日志可以通过一个点来判断GC是否正常——在一轮并发周期结束后需要确保堆剩下的空间小于InitiatingHeapOccupancyPercent的值。 3调整G1垃圾收集器的混合收集的工作量即在一次混合垃圾收集中尽量多处理一些分区可以从另外一方面提高混合垃圾收集的频率。在一次混合收集中可以回收多少分区取决于三个因素 1有多少个分区被认定为垃圾分区-XX:G1MixedGCLiveThresholdPercentn这个参数表示如果一个分区中的存活对象比例超过n就不会被挑选为垃圾分区因此可以通过这个参数控制每次混合收集的分区个数这个参数的值越大某个分区越容易被当做是垃圾分区 2G1在一个并发周期中最多经历几次混合收集周期这个可以通过-XX:G1MixedGCCountTargetn设置默认是8如果减小这个值可以增加每次混合收集收集的分区数但是可能会导致停顿时间过长 3期望的GC停顿的最大值由MaxGCPauseMillis参数确定默认值是200ms在混合收集周期内的停顿时间是向上规整的如果实际运行时间比这个参数小那么G1就能收集更多的分区。 五、G1的最佳实践
1. 关键参数项 -XX:UseG1GC告诉JVM使用G1垃圾收集器 -XX:MaxGCPauseMillis200设置GC暂停时间的目标最大值这是个柔性的目标JVM会尽力达到这个目标 -XX:INitiatingHeapOccupancyPercent45如果整个堆的使用率超过这个值G1会触发一次并发周期。记住这里针对的是整个堆空间的比例而不是某个分代的比例。
2. 最佳实践
不要设置年轻代的大小
通过-Xmn显式设置年轻代的大小会干扰G1收集器的默认行为 G1不再以设定的暂停时间为目标换句话说如果设置了年轻代的大小就无法实现自适应的调整来达到指定的暂停时间这个目标 G1不能按需扩大或缩小年轻代的大小 响应时间度量
不要根据平均响应时间ART来设置-XX:MaxGCPauseMillisn这个参数应该设置希望90%的GC都可以达到的暂停时间。这意味着90%的用户请求不会超过这个响应时间记住这个值是一个目标但是G1并不保证100%的GC暂停时间都可以达到这个目标
3. G1 GC的参数选项
参数名含义默认值-XX:UseG1GC使用G1收集器JDK1.8中还需要显式指定-XX:MaxGCPauseMillisn设置一个期望的最大GC暂停时间这是一个柔性的目标JVM会尽力去达到这个目标200-XX:InitiatingHeapOccupancyPercentn当整个堆的空间使用百分比超过这个值时就会触发一次并发收集周期记住是整个堆45-XX:NewRation新生代和老年代的比例2-XX:SurvivorRationEden空间和Survivor空间的比例8-XX:MaxTenuringThresholdn对象在新生代中经历的最多的新生代收集或者说最大的岁数G1中是15-XX:ParallelGCThreadsn设置垃圾收集器的并行阶段的垃圾收集线程数不同的平台有不同的值-XX:ConcGCThreadsn设置垃圾收集器并发执行GC的线程数n一般是ParallelGCThreads的四分之一-XX:G1ReservePercentn设置作为空闲空间的预留内存百分比以降低目标空间溢出疏散失败的风险。默认值是 10%。增加或减少这个值请确保对总的 Java 堆调整相同的量10-XX:G1HeapRegionSizen分区的大小堆内存大小的1/2000单位是MB值是2的幂范围是1MB到32MB之间-XX:G1HeapWastePercentn设置您愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比JavaHotSpotVM不会启动混合垃圾回收周期注意这个参数可以用于调整混合收集的频率。JDK1.8是5-XX:G1MixedGCCountTarget8设置并发周期后需要执行多少次混合收集如果混合收集中STW的时间过长可以考虑增大这个参数。注意这个可以用来调整每次混合收集中回收掉老年代分区的多少即调节混合收集的停顿时间8-XX:G1MixedGCLiveThresholdPercentn一个分区是否会被放入mix GC的CSet的阈值。对于一个分区来说它的存活对象率如果超过这个比例则改分区不会被列入mixed gc的CSet中JDK1.6和1.7是65JDK1.8是85
常见问题
1、Young GC、Mixed GC和Full GC的区别 答Young GC的CSet中只包括年轻代的分区Mixed GC的CSet中除了包括年轻代分区还包括老年代分区Full GC会暂停整个引用同时对新生代和老年代进行收集和压缩。
2、ParallelGCThreads和ConcGCThreads的区别 答ParallelGCThreads指得是在STW阶段并行执行垃圾收集动作的线程数ParallelGCThreads的值一般等于逻辑CPU核数如果CPU核数大于8则设置为5/8 * cpus在SPARC等大型机上这个系数是5/16。ConcGCThreads指的是在并发标记阶段并发执行标记的线程数一般设置为ParallelGCThreads的四分之一。
3、write barrier在GC中的作用如何理解G1 GC中write barrier的作用 写屏障是一种内存管理机制用在这样的场景——当代码尝试修改一个对象的引用时在前面放上写屏障就意味着将这个对象放在了写屏障后面。
write barrier在GC中的作用有点复杂我们这里以trace GC算法为例讲下trace GC有些算法是并发的例如CMS和G1即用户线程和垃圾收集线程可以同时运行即mutator一边跑collector一边收集。这里有一个限制是黑色的对象不应该指向任何白色的对象。如果mutator视图让一个黑色的对象指向一个白色的对象这个限制就会被打破然后GC就会失败。
针对这个问题有两种解决思路
1通过添加read barriers阻止mutator看到白色的对象
2通过write barrier阻止mutator修改一个黑色的对象让它指向一个白色的对象。write barrier的解决方法就是讲黑色的对象放到写write barrier后面。如果真得发生了white-on-black这种写需求一般也有多种修正方法增量得将白色的对象变灰将黑色的对象重新置灰等等。
我理解增量的变灰就是CMS和G1里并发标记的过程将黑色的对象重新变灰就是利用卡表或SATB的缓冲区将黑色的对象重新置灰的过程当然会在重新标记中将所有灰色的对象处理掉。关于G1中write barrier的作用可以参考R大的这个帖子里提到的 4、G1里在并发标记的时候如果有对象的引用修改要将旧的值写到一个缓冲区中这个动作前后会有一个write barrier这段可否细说下
答这块涉及到SATB标记算法的原理SATB是指start at the beginning即在并发收集周期的第一个阶段初始标记是STW的会给所有的分区做个快照后面的扫描都是按照这个快照进行在并发标记周期的第二个阶段并发标记这是收集线程和应用线程同时进行的这时候应用线程就可能修改了某些引用的值导致上面那个快照不是完整的因此G1就想了个办法我把在这个期间对对象引用的修改都记录动作都记录下来有点像mysql的操作日志。
5、GC算法中的三色标记算法怎么理解 trace GC将对象分为三类白色垃圾收集器未探测到的对象、灰色活着的对象但是依然没有被垃圾收集器扫描过、黑色活着的对象并且已经被垃圾收集器扫描过。垃圾收集器的工作过程就是通过灰色对象的指针扫描它指向的白色对象如果找到一个白色对象就将它设置为灰色如果某个灰色对象的可达对象已经全部找完就将它设置为黑色对象。当在当前集合中找不到灰色的对象时就说明该集合的回收动作完成然后所有白色的对象的都会被回收。
PS这个问题来自参考资料17我将原文也贴在下面 For a tracing collector (marking or copying), one conceptually colours the data white (not yet seen by the collector), black (alive and scanned by the collector) and grey (alive but not yet scanned by the collector). The collector proceeds by scanning grey objects for pointers to white objects. The white objects found are turned grey, and the grey objects scanned are turned black. When there are no more grey objects, the collection is complete and all the white objects can be recycled. 参考资料 Understanding G1 GC Logs Garbage First Garbage Collector Tuning 垃圾优先型回收器调优 Oracle的GC调优文档——G1 The Garbage-First Garbage Collector 《Java性能权威指南》 《Java性能调优指南》 G1入门O记官网的PPT Java Hotspot G1 GC的一些关键技术 G1 GC的论文 R大关于G1 GC的帖子 Tips for Tuning the Garbage First Garbage Collector Java性能调优指南 Java性能权威指南 G1: What are the differences between mixed gc and full gc? Part 1: Introduction to the G1 Garbage Collector Collecting and reading G1 garbage collector logs - part 2 GC FAQ -- algorithms