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

    科技2022-07-11  87

    Garbage-First Collector

    概述区域(Region)Humongous区域 特征G1的垃圾回收的过程年轻代回收(Young GC)并发标记过程混合回收(Mixed GC)Full GC 配置选项常见的配置方式

    概述

    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

    特征

    可控的 GC停顿时间内, 获得尽可能高的吞吐量当 GC线程回收速度慢时, 系统会使用用户线程, 给垃圾回收加速在回收期间并行性与并发性都存在由于堆内存分了若干个 Region, 所以回收时无需按整(如 Eden, Survivor或 Tenured)区块, 缩小了回收的范围, 因此对于回收期间发生的延迟有较好的控制因为 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

    处理过程:

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

    * 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图示

    Mixed GC图示

    Full GC

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

    配置选项

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

    常见的配置方式

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

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

    其它参数默认

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

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

    Processed: 0.010, SQL: 8