G1垃圾回收
G1
调优场景
Evacuation Failure
"evacuation failure", "to-space exhausted", "to-space overflow", "promotion failure"之类的字眼。这些术语的概念在G1 GC是相似的
巨型对象和巨型对象分配
对于 G1 GC,任何超过区域一半大小的对象都被视为“巨型对象”。此类对象直接被分配到老年代中的“巨型区域”。这些巨型区域是一个连续的区域集。StartsHumongous 标记该连续集的开始,ContinuesHumongous 标记它的延续。
在分配任何巨型区域之前,会检查标记阈值,如有必要,还会启动一个并发周期。
在清理阶段或完整的垃圾回收周期内,标记周期结束时会清理死亡的巨型对象。
为了减少复制开销,巨型对象未包括在疏散暂停中。完整的垃圾回收周期会对巨型对象进行压缩。
由于每个 StartsHumongous 和 ContinuesHumongous 区域集只包含一个巨型对象,所以没有使用巨型对象的终点与上个区域的终点之间的空间(即巨型对象所跨的空间)。如果对象只是略大于堆区域大小的倍数,则此类未使用的空间可能会导致堆碎片化。
如果巨型分配导致连续的并发周期,并且此类分配导致老年代碎片化,请增加 -XX:G1HeapRegionSize,这样一来,之前的巨型对象就不再是巨型对象了,而是采用常规的分配路径。
当未找到能放下巨型对象的连续分区时,G1会首先尝试扩展堆空间,扩展失败时,启动串行gull gc。
引用处理
-XX:PrintReferenceGC在GC日志中输出各种引用详细GC数据。
通常,在G1年轻代或者混合收集中,当PrintGCDetails输出中的Ref Proc时间超过GC暂停时间总数的10%时,需要进行调优:
1.-XX:+ParalleRefProcEnabled激活多线程引用处理,负面影响是会抢占用户线程时间片。
2.第一点作用不明显时,观察GC日志,找到导致Ref Proc处理时间过长的引用类型,优化应用程序减少对该类型引用的使用。
软引用特别说明:
由于软引用对象只会在OOM前回收,所以软引用对象可能会长期占用内存而频繁触发老年代收集周期,使用-XX:SoftRefLRUPolicyMSPerMB可以控制软引用对象回收时机,
回收周期
年轻代GC
对象从eden区分配失败时触发,copy eden + from survivor -> to survivor,超过晋升阈值的对象copy到老年代。
-
-XX:MaxGCPauseMillis 200ms
JVM会根据该参数调整年轻代大小。如果用户设置了-Xmn或者-XX:NewRatio等年轻代空间调整参数,会导致暂停目前参数失效。
-
-XX:G1NewSizePercent 5%
-
-XX:G1MaxNewSizePercent 60%
-
-XX:MaxTenuringThreshold 15
混合GC
触发时机:
当老年代分区占用总堆比例超过阈值(默认45%)时,触发混合GC。
初始标记:和下一次年轻代GC一起,STW并行标记,收集所有的GC Roots。
并发标记:多线程并发协同标示存活对象图。
重新标记:STW并行重新标记上个阶段产生的新垃圾。
并行回收:垃圾清理。
-
-XX:ConGCThreads
设置并行标记的线程数。默认值为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 。
增大该值将会占用应用线程处理时间,降低吞吐量,因为并发GC线程和应用线程同时工作。
-
初始标记
借道年轻代的STW执行,标记出GC Roots直接可达的对象,将NTAMS置到分区顶部。
- -XX:InitiatingHeapOccupancyPercent IHOP 45%
老年代占用比例超过该值时触发并发标记周期。
-
根分区扫描
年轻代存活对象作为“根分区”,扫描“根分区”对老年代的引用。
-
并发标记周期
使用基于写前栅栏的SATB快照标记算法(应用线程将发生更改前的引用放入stab_queue,由并发标记线程定期处理,以此找到标记开始时刻的存活对象快照),在多个并发标记周期不断交换PTAMS和NTAMS完成标记。
https://www.jianshu.com/p/9e70097807ba
-
重新标记
STW,GC线程并行处理所有生效的SATB缓冲区及所有更新(Rset更新?)。处理引用。
-
清除
识别所有空闲分区,清理空闲分区RSet,释放到空闲队列;
整理堆分区,为混合垃圾回收识别出高效率的老年代分区;
RSet梳理(比如,标记过程发现RSet中记录的某个对象已经死亡,将该记录从RSet中删除)。
高效率的分区:
1.存活对象少。
2.对象被别的分区对象引用数量少。这里数量多会导致RSet中PRT粒度粗化,增大RSet扫描开销。
交换位图、指针?并发标记周期也交换?
-
混合回收
-
-XX:G1MixedGCLiveThresholdPercent 85%
old generation region 中的存活对象的占比,低于该值时才会被选入 CSet。
-
-XX:G1MixedGCCountTarget 8
设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数。默认值是 8 次混合垃圾回收。混合回收的目标是要控制在此目标次数以内。
-
每次混合收集老年代CSet的最小数量 = 混合收集周期将回收的候选老年代分区总数/G1MixedGCCountTarge
- -XX:G1HeapWastePercent 10%
混合收集周期中发现垃圾占总堆比例低于该值时,停止混合收集周期
- -XX:G1OldCSetRegionThresholdPercent 10%
每次混合收集暂停收集分区上限,默认总堆的10%
Full GC
触发时机:对象分配失败时?
使用串行垃圾收集器对整个堆全面压缩。
G1的设计目标通过不断调优而不再需要full GC。
公共
-
-XX:G1HeapRegionSize
超过该值50%的对象将被视为巨型对象,直接分配在老年代。JDK 8u40之前,巨型对象由混合GC并发收集周期的清除阶段回收,之后可以在年轻代GC回收。
1MB~32MB,G1默认将堆划为为约2048个Region。
-
-XX:G1ReservePercent=10
设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认值是 10%。增加或减少百分比时,请确保对总的 Java 堆调整相同的量。
copy对象到to-space时产生对象晋升,老年代空间不足时会扩展老年代堆空间,老年代空间已达上限会产生晋升失败,增大预留内存占比可避免晋升失败。
-
-XX:ParallelGCThreads
设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。
如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。
堆空间调整
JVM通过在Xms和Xmx之间动态调整堆大小及年轻代大小,以满足用户设置的GC暂停时间MaxGCPauseMillis和GCTimeRatio(用户线程时间/GC线程时间)的目标。
RSet
引用关系
PRT 粒度?
关系维护
维护时机:
对象引用关系变化时(包括引用赋值、GC移动对象等),触发写栅栏代码,维护Rset。
若发生一个跨区引用关系变化,G1垃圾收集器会将相应的card加入到“脏卡片队列”。“并发优化线程”会扫描队列中的卡片来更新RSet。当“并发优化线程”来不及处理不过来时,会挂起用户线程,让用户线程也加入到更新Rset。
全局卡片表
在任意收集周期,扫描Rset与PRT时,会将扫描到的引用记录标记到全局卡片表,避免重复扫描。在收集周期的最后将该表清空,显示为Clear CT。
工作窃取机制
收集活动图
活动流程
关键算法
https://www.jianshu.com/p/548c67aa1bc0
RSET
-
写后栅栏
https://www.jianshu.com/p/870abddaba41
- 并发优化线程
-
写前栅栏
年轻代收集
如何确保新生代对象被老年代引用的时候不被gc?(查询老年代对象来确认对新生代对象的引用避免误回收)
机制:当老年代存活对象多时,每次minor gc查询老年代所有对象影响gc效率(因为gc stop-the-world),所以在老年代有一个write barrier(写屏障)来管理的card table(卡表),card table存放了所有老年代对象对新生代对象的引用。
所以每次minor gc通过查询card table来避免查询整个老年代,以此来提高gc性能。
初始标记
并发标记周期
混合收集周期
GC日志
跑出完成GC日志,标记日志说明。
https://www.jianshu.com/p/ac1ba3479c08
引用
堆外内存回收
https://www.jianshu.com/p/35cf0f348275
Reference&ReferenceQueue
https://www.jianshu.com/p/f0da6c1af815