Garbage-First Collector
概述
- 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: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等参数. 固定年轻代大小会覆盖暂停时间目标
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!