「后端」Java 程序员必知的 G1 垃圾回收器知识总结

昨天的文章《「后端」Java 程序员必知的 JVM 基础知识总结》中提到了垃圾回收,今天就来分享关于 G1 垃圾回收器的知识。欢迎阅读~

一、概述

G1 垃圾回收器(Garbage-First)并不新,是在 Java 7 update 4 时引入的一个新的垃圾回收器。官方在 ZGC 还没有出现时也推荐使用 G1 来代替选择 CMS。

G1 最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器的众多缺陷。G1 回收器和 CMS 比起来,有以下不同:

2、G1 的内存模型

G1 的内存模型

2.1 分区 Region

G1 采用了分区 (Region) 的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1 并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数 -XX:G1HeapRegionSize=n 可指定分区大小 (1MB~32MB,且必须是2的幂),默认将整堆划分为 2048 个分区。

2.2 卡片 Card

在每个分区内部又被分成了若干个大小为 512 Byte 卡片 (Card),标识堆内存最小可用粒度。所有分区的卡片,将会记录在全局卡片表 (Global Card Table) 中,分配的对象会占用物理上连续的若干个卡片,当查找分区内对象的引用时,便可通过卡片来查找该引用对象 (见 RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。

总之,G1 对内存的使用以分区 (Region) 为单位,而对对象的分配则以卡片 (Card) 为单位。

2.3 巨形对象 Humongous Region

一个大小达到甚至超过分区大小一半的对象称为巨型对象 (Humongous Object)。因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区 (Humongous Region)。G1 内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。

巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型 (StartsHumongous),相邻连续分区被标记为连续巨型 (ContinuesHumongous)。由于需要一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。

2.4 已记忆集合 Remember Set (RSet)

除了 Card Table 数组之外,每个 Region 还会有一个 RSet 数据结构。RSet 主要用来记录哪个 Region 的哪个 Card 上的对象引用了本 Region 中的对象。

实际实现中,RSet 默认是一个 HashMap,Map 的 key 是引用的 Region,value 是一个 List,List 中存储引用 Region 中的引用 Card 列表。Region、Card Table 以及 RSet 的示意图如下所示:

RSet

上图中,RegionA 和 RegionB 中分别有对象引用 RegionC 中的对象,在 RegionC 对应的 RSet 就会记录这样的引用关系。该 RSet 中有两个 KV 对,第一个 KV 的 key 是 RegionA,value 是一个列表,列表中有两个元素 3 和 65534,分别代表 RegionA 中引用对象在对应 Card Table 中的下标。第二个 KV 对的 key 是 RegionB,value 中列表只有一个元素 1565,代表 RegionB 中引用对象在对应 Card Table 中的下标。

2.5 收集集合 (CSet)

CSet 收集示意图

CSet

收集集合 (Collection Set) 代表每次 GC 暂停时回收的一系列目标分区。在任意一次收集暂停中,CSet 所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。因此无论是年轻代收集,还是混合收集,工作的机制都是一致的。年轻代收集 CSet 只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到 CSet 中。

候选老年代分区的 CSet 准入条件,可以通过活跃度阈值 -XX:G1MixedGCLiveThresholdPercent (默认85%) 进行设置,即只有存活对象低于 85% 的 Region 才可能被回收,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据 CSet 对堆的总大小占比 -XX:G1OldCSetRegionThresholdPercent (默认10%) 设置数量上限,即老年代一次最大收集总内存的 10%。

由上述可知,G1 的收集都是根据 CSet 进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。

三、G1 的收集过程

3.1 年轻代收集

G1 的 YoungGC 和 CMS 的 Young GC,其标记-复制全过程 STW。

Young GC

3.2 混合收集

年轻代收集不断活动后,老年代的空间也会被逐渐填充。当老年代占用空间超过整堆比阈值 -XX:InitiatingHeapOccupancyPercent (默认 45%) 时,G1 就会启动一次混合垃圾收集周期。

为了满足暂停目标,G1 可能不能一口气将所有的候选分区收集掉,因此 G1 可能会产生连续多次的混合收集与应用线程交替执行,每次 STW 的混合收集与年轻代收集过程相类似。

混合收集

G1 的混合回收过程可以分为标记阶段、清理阶段和复制阶段。

标记阶段停顿分析

清理阶段停顿分析

复制阶段停顿分析

四个 STW 过程中,初始标记因为只标记 GC Roots,耗时较短。再标记因为对象数少,耗时也较短。清理阶段因为内存分区数量少,耗时也较短。转移阶段要处理所有存活的对象,耗时会较长。因此,G1 停顿时间的瓶颈主要是标记-复制中的转移阶段 STW。为什么转移阶段不能和标记阶段一样并发执行呢?主要是 G1 未能解决转移过程中准确定位对象地址的问题。

四、参数优化

上文简单介绍了 G1 的工作原理,知道原理后,在我们实际使用 G1 过程中,再配合一些常用参数的设置,就能更好的优化程序的运行。

-XX:MaxGCPauseMillis

GC 最大暂停时间,默认 200ms。这是一个软性目标,G1会尽量达成,如果达不成,会逐渐做自我调整。

对于 Young GC,会逐渐减少 Eden 区个数,减少 Eden 空间那么 Young GC 的处理时间就会相应减少。

对于 Mixed GC,G1 会调整每次 Cset 的比例,默认最大值是 10%,当然每次选择的 Cset 少了,所要经历的 Mixed GC 的次数会相应增加。

Cset

减少 Eden 的总空间时,就会更加频繁的触发 Young GC,也就会加快 Mixed GC 的执行频率,因为 Mixed GC 是由 Young GC 触发的,或者说借机同时执行的。频繁 GC 会对对应用的吞吐量造成影响,每次 Mixed GC 回收时间太短,回收的垃圾量太少,可能最后 GC 的垃圾清理速度赶不上应用产生的速度,那么可能会造成串行的 Full GC,这是要极力避免的。

所以暂停时间肯定不是设置的越小越好,当然也不能设置的偏大,转而指望 G1 自己会尽快的处理,这样可能会导致一次全部并发标记后触发的 Mixed GC 次数变少,但每次的时间变长,STW 时间变长,对应用的影响更加明显。

-XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent

新生代比例有两个数值指定,下限:-XX:G1NewSizePercent,默认值 5%,上限:-XX:G1MaxNewSizePercent,默认值 60%。

G1 会根据实际的 GC 情况 (主要是暂停时间) 动态的调整新生代的大小,主要是 Eden Region 的个数。最好是 Eden 的空间大一点,因为 Young GC 的频率更高,大的 Eden 空间能够降低 Young GC 的发生次数。但同时也需要平衡好 Mixed GC 中新生代和老年代的 Region,如果 Eden 很大,那么留给老年代回收空间就不多了,最后可能会导致 Full GC。

当然,G1 依然可以设置固定的年轻代大小 (参数 -XX:NewRatio、-Xmn),但同时暂停目标将失去意义。

-XX:G1MixedGCLiveThresholdPercent

指定被纳入 Cset 中 Region 的存活空间占比阈值,默认 85%。在全局并发标记阶段,如果一个 Region 的存活对象的空间占比低于此值,才有可能被纳入 Cset。

此值直接影响到 Mixed GC 选择回收的区域,当发现 GC 时间较长时,可以尝试调低此阈值,尽量优先选择回收垃圾占比高的 Region,但此举也可能导致垃圾回收的不够彻底,最终触发 Full GC。

-XX:InitiatingHeapOccupancyPercent

指定触发全局并发标记的老年代使用占比,默认值 45%,也就是老年代占堆的比例超过 45%。

如果 Mixed GC 结束后老年代使用率还是超过 45%,那么会再次触发全局并发标记过程,这样就会导致频繁的老年代 GC,影响应用吞吐量。同时老年代空间不大,Mixed GC 回收的空间肯定是偏少的。如果此值太高,很容易导致年轻代晋升失败而触发 Full GC,所以需要多次调整测试。

五、总结

G1 是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的 GC 暂停目标,就能得到不错的性能。

文章来源:https://www.jianshu.com/p/b530907c0fc3

展开阅读全文

页面更新:2024-05-11

标签:垃圾   程序员   分区   标记   大小   对象   内存   阶段   年代   年轻   知识   空间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top