【JVM】深入解读 G1 垃圾回收器
前言
📫作者简介:小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫
🏆 InfoQ 签约博主、CSDN 专家博主/Java 领域优质创作者/CSDN 内容合伙人、阿里云专家/签约博主、华为云专家、51CTO 专家/TOP 红人 🏆
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
本文导读
你了解 G1 垃圾回收器吗,有 G1 还需要其他的回收器?有了 G1 为什么还要 ZGC?不懂?一文带你搞懂 G1 垃圾回收器。
一、G1 垃圾回收器
传统的垃圾回收器一般情况下 内存占用、吞吐量、延时 只能同时满足两个。但是现在的发展,延迟这项的目标越来越重要。所以就有低延迟的垃圾回收器。
1、设计思想
随着 JVM 中内存的增大,STW 的时间成为 JVM 急迫解决的问题,但是如果按照传统的分代模型,总跳不出 STW 时间不可预测这点。为了实现 STW 的时间可预测,首先要有一个思想上的改变。G1 将堆内存“化整为零”,将堆内存划分成多个大小相等独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。回收器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
2、Region
Region 可能是 Eden,也有可能是 Survivor,也有可能是 Old,另外 Region 中还有一类特殊的 Humongous 区域,专门用来存储大对象。 G1 认为只要大小超过了一个 Region 容量一半的对象即可判定为大对象。每个 Region 的大小可以通过参数-XX:G1HeapRegionSize 设定,取值范围为 1MB~32MB,且应为 2 的 N 次幂。而对于那些超过了整个 Region 容量的超级大对象,将会被存放在 N 个连续的 Humongous Region 之中,G1 的进行回收大多数情况下都把 Humongous Region 作为老年代的一部分来进行看待。
二、G1 参数设置
开启参数-XX:+UseG1GC 启用垃圾优先(G1) 垃圾收集器的使用。它是一种服务器样式的垃圾收集器,适用于具有大量 RAM 的多处理器计算机。它极有可能满足 GC 暂停时间目标,同时保持良好的吞吐量。建议将 G1 收集器用于需要大堆(大小约为 6 GB 或更大)且 GC 延迟要求有限(稳定且可预测的暂停时间低于 0.5 秒)的应用程序。
分区大小-XX: G1HeapRegionSize=大小设置使用垃圾优先(G1)收集器时将 Java 堆细分为的区域的大小。取值范围是 1 MB 到 32 MB。默认区域大小是根据堆大小根据人体工程学确定的。以下示例显示如何将细分的大小设置为 16 MB:-XX: G1HeapRegionSize = 16m。一般建议逐渐增大该值,随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长。
最大 GC 暂停时间-XX: MaxGCPauseMillis =时间设置最大 GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且 JVM 将尽最大的努力来实现它。默认情况下,没有最大暂停时间值。下面的示例显示如何将最大目标暂停时间设置为 500 ms:-XX: MaxGCPauseMillis = 500。
三、G1 运行过程
G1 的运作过程大致可划分为以下四个步骤:
初始标记( Initial Marking)
仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿。TAMS 是什么?要达到 GC 与用户线程并发运行,必须要解决回收过程中新对象的分配,所以 G1 为每一个 Region 区域设计了两个名为 TAMS(Top at Mark Start)的指针,从 Region 区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。
并发标记( Concurrent Marking)
从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,并发时有引用变动的对象,这些对象会漏标( 后续再讲三色标记的时候会细讲这个问题),漏标的对象会被一个叫做 SATB(snapshot-at-the-beginning)算法来解决(这个下节课会细讲)
最终标记( Final Marking)
对用户线程做另一个短暂的暂停,用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。
筛选回收( Live Data Counting and Evacuation)
负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
四、GC 日志详解
五、G1 常用参数
-XX:+UseG1GC 启用 CMS 垃圾收集器
-XX:MaxGCPauseMillis 设置最大 GC 暂停时间的目标(以毫秒为单位)。这是一个软目标,并且 JVM 将尽最大的努力(G1 会尝试调整 Young 区的块数来)来实现它。默认情况下,没有最大暂停时间值。
-XX:GCPauseIntervalMillis GC 的间隔时间
-XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着 size 增加,垃圾的存活时间更长,GC 间隔更长,但每次 GC 的时间也会更长
-XX:G1NewSizePercent 新生代最小比例,默认为 5%
-XX:G1MaxNewSizePercent 新生代最大比例,默认为 60%
-XX:GCTimeRatioGC 时间建议比例,G1 会根据这个值调整堆空间
-XX:ConcGCThreads 线程数量
-XX:InitiatingHeapOccupancyPercent 启动 G1 的堆空间占用比例,根据整个堆的占用而触发并发 GC 周期
总结
G1 垃圾回收器的回收过程,并着重看了一下底层的数据结构 RSet。相对于 CMS,G1 有了更可靠的驾驭度。而且有 RSet 和 SATB 等算法的支撑,Remark 阶段更加高效。G1 最重要的概念,其实就是 Region。它采用分而治之,部分收集的思想,尽力达到我们给它设定的停顿目标。G1 的垃圾回收过程分为三种,其中,并发标记阶段,为更加复杂的 Mixed GC 阶段做足了准备。
版权声明: 本文为 InfoQ 作者【小明Java问道之路】的原创文章。
原文链接:【http://xie.infoq.cn/article/483a0b87ca7ecdeac37ffaf05】。文章转载请联系作者。
评论