JVM垃圾回收-G1收集器(六)

概述

  • G1是一款面向服务端的垃圾收集器, 主要针对具有大内存以及多处理器的机器
  • Jdk7开始正式使用 启用参数为 -XX:+UseG1GC, Jdk9时成为默认垃圾收集器, 取代了 CMS以及 Parallel& Parallel Old的组合
  • 与 CMS相比, 当小内存环境可能 CMS优于 G1, 或在大内存环境, 则 G1优于 CMS. 两种收集器的平衡点在6~8G之间(如果高了就是 G1会更优)

区域(Region)

  • G1的堆内存被划分成约2048个大小相同且独立 Region, 每个 Region的大小范围是1~32MB之间, 就是2的幂, 即1MB, 2MB … 16MB, 32MB. 设置参数: -XX:G1HeapRegionSize=N
  • 虽然分了多个 Region, 但逻辑上依然存在分代结构, 比如使用若干个不同 Region来表示新年代(Eden区和幸存者0/1区)或老年代. 一个 Region只可能属于一个区(如 Eden, Survivor或 Tenured)
  • G1不强制要求新年代或老年代的空间必须为连续的, 甚至 GC后被回收的 Region可能还会变成其它区/代的 Region
  • 按垃圾堆积的价值(不可触及对象占比多的)大小顺序后台维护一个优先列表, 优先回收回收价值高的 Region. 尽可能在设定的可接受的(延迟)时间内完成一次 GC. 设置参数: -XX:MaxGCPauseMillis=200, 默认值为200ms
  • 决定回收的 Region是通过复制算法, 将留存的存活对象转移到空的 Region中

Humongous区域

  • 在 G1堆中, 有一种特殊的区域, 叫 Humongous. 主要用于存储大对象, 当某个对象大小超过了 1.5个 Region, 就会存到 Humongous区域中
  • 当存储大对象到 Humongous区域中时, 有时为了空出连续的 Humongous区域, 不得不触发 Full GC

特征

  1. 可控的 GC停顿时间内, 获得尽可能高的吞吐量
  2. 当 GC线程回收速度慢时, 系统会使用用户线程, 给垃圾回收加速
  3. 在回收期间并行性与并发性都存在
  4. 由于堆内存分了若干个 Region, 所以回收时无需按整(如 Eden, Survivor或 Tenured)区块, 缩小了回收的范围, 因此对于回收期间发生的延迟有较好的控制
  5. 因为 Region的回收是通过复制算法来实现的, 所以不会产生内存碎片. 此特征可以明显的减少分配大对象时, 因无法找到连续的内存空间而触发 Full GC的概率

G1的垃圾回收的过程

  • Remembered Set(记忆集), 简称 RSet
    (-) 一个对象被不同区域(如 Eden, Survivor或 Tenured)之间有引用关系时, 此时 JVM为了避免全堆扫描, 专门开辟一块空间记录了引用关系, 这就是 Remembered Set(内部实现是 HashTable)
    * 在 Minor GC时, 首先枚举根节点. 根节点可能在新生代或老年代中. 由于是 Minor GC, 所以没有必要对老年代的 GC Roots做全面的可达性分析. 但老年代中可能会存在指向新生代中对象的引用, 此时可以通过 Remembered Set进一步确认引用关系后进行回收
    (-) G1以外其它所有 JVM分代收集器都有这个问题, 且都是使用 Remembered Set来处理的
    (-) 每个 Region都拥有一个对应的 Remembered Set

处理过程:

  1. 程序对 Reference数据写操作时, 会产生一个 Write Barrier暂时中断操作
  2. 判断指定的对象和 Reference引用是否在同一个 Region中
  3. 如果不在同一个 Region, 通过 Card Table, 将相关引用信息, 记录到被引用的对象所属 Region对应的 Remembered Set中
    当进行内存回收时, 在 GC根节点的枚举范围中加入对象所属 Remembered Set. 即可保证不做全堆扫描也不会有遗漏

图 (RSet and Card)

* RSet与 Card的关系. 每个 Region被分成了多个 Card, 其中颜色为绿色的 Card表示该 Card中有对象引用了其它 Card中的对象. RSet的数据结构是 HashTable, 其中 Key是 Region的起始地址, Value是 Card Table(字节数组), 该字节数组的下标表示 Card的空间地址, 当指定空间地址被引用的时候, 会被标记为 dirty_card
`* 每当引用赋值时, 不会直接更新 RSet, 而是会先入队到 dirty card queue中, 再等下次垃圾回收时, 将 dirty card queue中的所有 card做处理, 以此更新 RSet

年轻代回收(Young GC)

  • G1的年轻代回收阶段是一个并行的独占式收集器. 在年轻代回收期, 暂停所有用户线程, 然后将存活对象从 Eden区移到 Survivor区或老年代中, 从 Survivor区移到老年代中
  • GC时, 所有的用户线程会 STW, G1创建回收集(Collection Set, 简称 CSet), 回收集是用于记录需要被回收的内存分段的集合, 它包含 Eden区和 Survivor区的所有内存分段

(-) 第1阶段, 扫描根: 根是指 static变量的指向或运行中的方法的局部变量(如 引用类型). 根引用连同 RSet记录的外部引用作为扫描存活对象的入口
(-) 第2阶段, 更新 RSet: 处理 dirty card queue中的 card, 更新 RSet. 此阶段完成后, RSet可以准确的反映老年代对所在的内存分段中对象的引用
(-) 第3阶段, 处理 RSet: 识别被老年代某对象指向的 Eden区中的对象
(-) 第4阶段, 复制对象: 遍历对象树, Eden区内存段中存活的对象会被复制到 Survivor区中空的内存分段, 然后将 Survivor区内存分段中的存活的对象, 判断年龄: 达到阀值的对象复制到老年代中空的内存分段中, 未达到的对象年龄累加1. 如果 Survivor区空间不足, 部分数据会直接从 Eden区晋升到老年代空间
(-) 第5阶段, 处理引用: 处理 Soft, Weak, Phantom, Final, JNI Weak等引用

并发标记过程

  • 当堆空间被占一定阈值时, 就会开始并发标记过程. 触发 GC的空间占比设置参数: -XX:InitiatingHeapOccupancyPercent=45, 默认值为 45%

(1) 初始标记阶段: 标记从根节点直接可达的对象. 这个阶段是 STW的, 且会触发一次年轻代 GC
(2) 根区域扫描(Root Region Scanning): G1 GC扫描 Survivor区可达的老年代区域对象, 并标记被引用的对象. 此过程必须在 Young GC之前完成
(3) 并发标记(Concurrent Marking): 在整个堆中进行并发标记(与用户线程是并发执行的), 此过程可能会被 Young GC中断. 在并发标记阶段, 若发现指定 Region内的的所有对象都是垃圾, 那这个 Region会被立即回收. 同时, 并发标记过程中, 会计算每个 Region的存活对象的占比(为了判断值不值得回收)
(4) 再次标记(Remark, STW): 由于程序持续运行中, 需要修正上一次的标记结果. G1中采用了比 CMS更快的初始快照算法: snapshot-at-the-beginning(SATB)
(5) 独占清理(cleanup, STW): 计算各个 Region的存活对象和 GC回收比例, 并进行排序, 识别可以混合回收的 Region. 为下阶段做铺垫 (注: 此阶段并不会做实际的垃圾的收集)
(6) 并发清理阶段: 识别并清理空闲的区域

混合回收(Mixed GC)

  • Mixed GC回收包括整个 Young Region和 部分 0ld Region
  • 在并发标记结束以后, 老年代中百分百为垃圾的内存分段被回收了, 部分为垃圾的内存分段被计算了出来. 这些老年代的内存分段会分N次被回收, 设置参数 -XX:G1MixedGCCountTarget=8, 默认值为8
  • 混合回收的收集器(Collection Set)包括八分之一的老年代内存分段, Eden区内存分段, Survivor区内存分段. 混合回收的算法和年轻代回收的算法完全一样, 只是回收集多了老年代的内存分段. 具体过程请参考上面的年轻代回收过程
  • 由于老年代中的内存分段默认分8次回收, G1会优先回收垃圾多的内存分段. 垃圾占内存分段比例越高的, 越先回收. 并且有一个阈值会决定内存分段是否该回收, -XX:G1MixedGCLiveThresholdPercent=65, 默认值为65%, 意思是垃圾占内存分段比例要达到65%才会被回收. 如果垃圾占比太低, 意味着存活的对象占比高, 在复制时会花费更多的时间
  • 混合回收不一定要进行8次. 有一个阈值 -XX:G1HeapWastePercent=10, 默认值为10%, 意思是允许整个堆内存中有10%的空间被浪费, 所以当发现可以回收的垃圾占堆内存的比例低于10%, 则不进行混合回收
  • Young GC图示
    图(Region)

  • Mixed GC图示
    图(混合回收)

Full GC

  • G1的初衷就是要避免Full GC的出现, 一旦发生就是需要进行调整
  • 导致G1 Full GC的原因可能有两个:
  1. 当 Evacuation(复制存活对象)时, 没有足够的 to-space来存放晋升的对象
  2. 并发处理过程未完成之前空间耗尽. 也就是制造垃圾比起回收垃圾还要快的情况

配置选项

参数 说明
-XX:+UseG1GC 显式指定 G1垃圾收集器
-XX:G1HeapRegionSize 设置每个 Region的大小, 大小范围是1~32MB之间, 就是2的幂, 即1MB, 2MB … 16MB, 32MB. 目标是根据最小的 Java堆大小划分出约2048个区域. 默认为堆内存的1/2000
-XX:MaxGCPauseMillis 200 设置期望达到的最大 GC停顿时间. 默认值为200ms
-XX:ParallelGCThreads 8 设置收集器的线程数
-XX:ConcGCThreads 设置并发标记的线程数. 将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
-XX:InitiatingHeapOccupancyPercent 45 设置触发 GC的堆占比, 默认值为 45%
-XX:G1MixedGCCountTarget 8 混合垃圾回收的目标次数, 默认值为8
-XX:G1MixedGCLiveThresholdPercent 65 指定垃圾占内存分段比率, 一旦到此比率就会被回收, 默认值为65%
-XX:G1HeapWastePercent 10 设置允许浪费的堆占比, 如果未达到此阈值, 则不进行混合回收, 默认值为10%

常见的配置方式

  • G1的设计原则是简化 JVM性能调优, 开发人员只需要简单的三步即可完成调优

第一步: 开启 G1垃圾收集器
第二步: 设置堆大小
第三步: 设置最大的停顿时间

  • 其它参数默认

注: 避免显式设置 -Xmn和 -XX:NewRatio等参数. 固定年轻代大小会覆盖暂停时间目标

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

猜你喜欢

转载自blog.csdn.net/qcl108/article/details/108912940