常用垃圾回收器对比

2022/12/28 java

常用垃圾回收器对比


[TOC]

背景

两种标记算法

算法 说明 特点
引用计数法 在堆内存中分配对象时,会为对象分配一段额外的空间,这个空间用于维护一个计数器:
如果有一个新的引用指向这个对象,则计数器+1
如果指向该对象的引用被置空或指向其它对象,则计数器-1
当计数器的值为0时,则自动删除这个对象。
1.循环引用问题
2.多线程维护计数器问题
可达性分析法 以根集合(GCRoot)作为起始点,从这些节点出发,根据引用关系开始搜索,所经过的路径称为引用链,当一个对象没有被任何引用链访问到时,则证明此对象是不活跃的,可以被回收  

什么是循环引用?

有对象 A 和对象 B,对象 A 中含有对象 B 的引用,对象 B 中含有对象 A 的引用。此时,对象 A 和对象 B 的引用计数器都不为 0。但是在系统中却不存在任何第 3 个对象引用了 A 或 B。也就是说,A 和 B 是应该被回收的垃圾对象,但由于垃圾对象间相互引用,从而使垃圾回收器无法识别,引起内存泄漏。

可以作为GCRoot的对象

序号 对象 范围 描述
1 方法区中的类静态属性引用的对象 全局对象 Class对象。
2 方法区中常量引用的对象 全局对象 例如字符串常量池,本身初始化后不再改变。
3 虚拟机栈中(栈帧中的本地变量表)引用的对象 执行上下文 属于执行上下文中的对象,线程在执行方法时,会将方法打包成一个栈帧入栈执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在运行,还没出栈,就意味这本地变量表的对象还会被访问,GC就不应该回收,所以这一类对象也可作为GC Roots。
4 本地方法栈中 JNI(Native 方法) 引用的对象 执行上下文 类似虚拟机栈中引用对象,区别是native方法。
5 被同步锁持有的对象 执行上下文 当前有线程持有对象锁的情况下,被synchronized锁住的对象不能回收,否则会导致锁失效。

什么是GCRoot?

JVM确定当前绝对不能被回收的对象。

常见垃圾回收算法

算法 说明 适用场景 特点
标记-复制 标记阶段:从根节点开始标记所有被引用的对象(活动对象)
复制阶段:内存被划分为两个大小相同区域,每一次只使用一个区域,回收时将存活对象复制到另一个区域,再清空原区域
适用于年轻代:存活对象较少的情况 需要两倍内存空间
需要复制移动对象
标记-清除 标记阶段:从根节点开始标记所有被引用的对象(活动对象)
清除阶段:将未标记的对象清除
适用于老年代:存活对象较多的情况 因为清理后内存空间不连续,容易产生内存碎片
标记-整理 标记阶段:从根节点开始标记所有被引用的对象(活动对象)
整理阶段:将存活对象向头部移动,然后清除尾部以外的内存
适用于存活对象较少的情况 不会产生内存碎片
效率低于标记-清除算法

主要的垃圾回收性能指标

指标 描述
更高的GC效率 GC线程串行独占式运行,没有线程切换开销,且运行时暂停所有用户线程,能以最高效率回收非存活对象。
更高的吞吐量 吞吐量指在应用程序的生命周期内,应用程序所花费的时间和系统总运行时间的比值。
GC线程运行时会暂停用户线程,用户线程暂停时间越短,用户线程运行时间越长,系统吞吐量越高。
更短的STW停顿时间 GC线程并发运行,用户线程单次暂停时间越短,系统响应时间越短。

什么是STW?

STW全称Stop The World,即GC线程运行时暂停其它用户线程,为了保持对象引用关系不变,GC时能够准确的标记出存活对象。

常见垃圾回收器

回收器 内存区间 回收算法 GC线程运行方式 GC线程工作模式 STW停顿时间 优先指标 适用CPU核数 主要特性 描述
Serial 新生代 标记-复制 串行 独占 停顿 更高的GC效率 单核 1.GC时会暂停所有应用线程 适合堆空间较小、单核CPU串行的收集器
ParNew 新生代 标记-复制 并行 独占 停顿 更短的STW停顿时间 多核 1.多核CPU情况下,GC线程并行以减少STW耗时,单核CPU则会因为线程切换效率更差 相当于Serial的多线程版本,一般和CMS配合使用
Parallel Scavenge 新生代 标记-复制 并行 独占 停顿 更高的吞吐量 多核 1.能控制最大STW停顿时间,以高效利用CPU实现更高的吞吐量 适合多核CPU,不需要太多交互的场景并行的收集器
Serial Old(PS MarkSweep) 老年代 标记-整理 串行 独占 停顿 更高的GC效率 单核 1.GC时会暂停所有应用线程
2.老年代存活对象较多,因此整理而不是复制
Serial的老年代版本
适合单核CPU串行的收集器(Parallel MarkSweep 和 Serial Old 实现相似)
Parallel Old 老年代 标记-整理 并行 独占 停顿 更高的吞吐量 多核 1.多核CPU情况下,GC线程并行以减少STW耗时 Parallel Scavenge的老年代版本
CMS 老年代 标记-清除 并行 并发 小停顿 更短的STW停顿时间 多核 1.拆分标记阶段以降低STW停顿时间
2.清除时内存碎片较多
为了GC线程与用户线程并发,共用内存,故采用清除算法而不是复制算法
G1   标记-整理/标记-复制 并行 并发 可预测小停顿 更短的STW停顿时间 多核 1.可预测STW停顿时间 适合多核CPU不划分连续的年轻代和老年代;通过计算老年代对象效益率,优先回收最大效益对象,来预测停顿时间
ZGC   标记-复制 并行 并发 更小停顿 更短的STW停顿时间 多核 1.极短的STW停顿时间,可能降低吞吐量 适合对长尾请求P99要求高的系统

CMS

什么是CMS?

CMS全称Concurrent Mark Sweep,是一款以低停顿为目标的垃圾回收器,这个回收器是一款真正意义的并发收集器。

CMS是针对老年代的垃圾回收器,采用了标记-清除算法。

CMS的GC过程

序号 GC步骤 是否STW 与用户线程 说明
1 初始标记 STW GC线程可并行
用户线程停顿
从GC Root出发,标记所有GC Root直接关联的老年代存活的对象(可达性分析)
只有STW的时候才能准确标记到存活对象
2 并发标记 GC线程与用户线程并发 从初始标记的对象开始,遍历整个对象引用链,标记GC Root最终可达存活对象
并发标记与用户线程并发过程中,部分引用关系已变化,JVM将已变化的区域标记为“脏区”,预先找到“脏区”并刷新引用关系
3 最终标记/重新标记 STW 用户线程停顿 重新从GC Root出发标记所有的存活对象
4 并发清理 GC线程与用户线程并发 清理非存活对象,老年代的存活对象较多,因此只清理非存活对象
5 并发重置 GC线程与用户线程并发 重新调整堆大小,重置卡表的标记等

卡表和脏区:

YGC时为了标记活动标记对象除了从GC Root开始扫描外, 另外还有老年代里也会引用新生代对象。所以正常来说还要扫描一次老年代,如果是扫描整个老年代(相当于将整个老年代作为GC Root)这将会随着堆的增大变得越来越慢,特别是现在内存都越来越大了。所以为了提升性能就引入卡表。

逻辑上把老年代内存分成一个个大小相等的卡片,然后对每个卡片准备一个与其对应的标记位,并将这些位集中起管理就好像一个表格一样,改写对象引用是从老年代指向新生代时,在老年代对应的卡片标记位上设置标志位即可,通常这样的卡片我们称之为“脏区”。

btye[i] = 1 就表示第i + 1 卡片所在内存上有指向新生代引用的老年代对象,这时只要tracing这个卡片上的对象即可。

如果每个card大小的是128字节(1024位,)那卡表就只占整个老年代的1/1024之一。所以遍历卡表的时间会远比遍历整个老年代快得多。

CMS的特点

序号 特点
1 以降低响应速度优先,只在初始标记阶段+最终标记阶段会STW,其它标记阶段GC线程和用户线程并发运行,尽可能减少标记阶段STW时间
2 无法控制STW的耗时,耗时与扫描对象个数正相关,耗时随堆内存的增大而增加
3 清除非存活对象会产生内存碎片,不过CMS会在FGC进行内存压缩,当回收的堆空间不够,则会回退到Serial Old进行标记-整理,最终导致STW变长
4 因为标记阶段GC线程并发,会占用CPU资源,最终导致吞吐量下降
5 因为标记阶段为了保证标记到所有存活对象,一些非存活对象也被标记成了存活对象,只能等下一次GC

G1

什么是G1?

G1的内存模型

G1的GC过程

ZGC

什么是ZGC?

ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

ZGC的GC过程

总结

CMS、G1和ZGC特性对比

  CMS G1 ZGC
       
       
       
       
       
       

某运价系统从PS Scavenge和PS MarkSweep切换为ZGC后的效果

请求平均耗时(缺失P99监控),从15ms降低到10ms,效果明显

image-20221227150833397

GC次数增多,GC时间从35ms降低到3ms

新的ZGC Pauses平均次数=3.9次/秒

image-20221227172903230

新的ZGC Pauses GC平均耗时=3.31ms

image-20221227172958092

旧的年轻代PS Scavenge YGC平均次数=0.1521次/秒

image-20221227172424929

旧的年轻代PS Scavenge YGC平均耗时=35ms

image-20221227172506805

ZGC实践参考