本文主要介绍G1垃圾收集器的设计思想、实现细节及调优策略(转载请注明出处,若有错误欢迎批评指正)
设计目标
可控延迟下获得尽可能高的吞吐量
应用场景
大内存多处理器的服务端
主要思想
- 将内存划分为小的Region作为内存回收单位,内存回收时间更短。每个Region均可被指定为新生代或老年代
- 进行Region的统计信息计算回收收益和成本,优先回收收益较大的Region
- 分代收集思想,频繁回收新生代,偶尔回收老年代。 普通对象分配在新生代,大对象分配到老年代
- 回收时使用标记复制算法
工作过程
- 初始标记:STW短时停顿,标记GCRoots
- 并发标记:从GCRoot出发进行可达性分析标记,与用户线程并发执行。扫描完成后SATB修正
- 最终修正:STW短时停顿,完成全部SATB修正
- 筛选回收:评估回收价值和成本,制定回收计划。STW短时停顿,多线程并行使用标记-复制算法将回收Region的存活对象复制到空Region,然后清空原Region
如何解决并发标记错误
并发错误
DFS标记在与用户线程并发执行时,用户线程会更改对象引用关系,造成 不该删除的节点被误删,以导致严重的并发错误。而该删除的节点没删除则仅仅占用内存,可在后续垃圾回收时再标记删除,称为 浮动垃圾
对象在创建时都是由GCRoots产生并关联的,意味着节点最初都是GCRoots可达的。可达性分析后,所有可达节点都被DFS扫描为黑色,所有不可达节点最终保持白色
DFS已扫描的节点不再扫描,不再扫描的节点新添加对不可达节点的引用,导致不可达节点最终仍未被扫描,并发错误的根源是并发标记结束时出现了黑→白引用,即同时满足 新增黑→白引用 和 删除所有指向该未扫描白色节点引用(不可达的白色节点产生原因) 两个条件
SATB
G1采用SATB(Snapshot At The Beginning,原始快照)方式,即暂时保留删除引用的白色节点,打破了第二个条件来解决并发标记错误。G1的SATB实现中,本轮并发标记过程中修改引用的节点记入队列,并在之后作为GCRoot重新扫描。事实上,引用修改同时包括了新增黑→白情况(如下图Round2b)和删除指向白引用(如下图Round1)情况,同时解决了以上两问题。详细分析如下图
写前屏障和写后屏障
如何解决跨Region引用
1)应将所有持有待删对象引用的对象加入GCRoot,以免造成误删
2)应避免扫描整个引用Region,采用分块思想,记录GCRoot所在Region的内存块索引以计算内存块地址
优缺点
- 内存使用灵活
- 不会产生内存碎片:标记复制算法避免产生内存碎片
- 在延迟和吞吐量之间做出平衡:基于Region的策略降低了单次回收停顿时间
- 占用内存较高:每个Region都有一份卡表保存跨Region引用
- 执行负载较高:写前屏障记录并发标记过程中的指针变化,写后屏障维护卡表
调优策略
参考资料
周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践[M]. 第3版. 机械工业出版社, 2019.
杨易. 深入解析Java虚拟机HotSpot[M]. 1. 机械工业出版社, 2020.
HotSpot Virtual Machine Garbage Collection Tuning Guide
Getting Started with the G1 Garbage Collector