前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >各种垃圾回收算法及收集器

各种垃圾回收算法及收集器

原创
作者头像
在下是首席架构师
发布于 2022-08-02 13:08:23
发布于 2022-08-02 13:08:23
36602
代码可运行
举报
文章被收录于专栏:从入门到出门从入门到出门
运行总次数:2
代码可运行
回收算法
标记-清除算法

该算法分为“标记”和“清除”阶段:首先比较出所有需要回收的对象,在标记完成后统一回收掉所有被标 记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来 两个明显的问题:

  1. 效率问题
  2. 空间问题(标记清除后会产生大量不连续的碎片)
复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一 块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。 这样就使每次的内存回收都是对内存区间的一半进行回收。

标记-整理算法

根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对 可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不 同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合 适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制 成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分 配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

垃圾收集器分类

串行垃圾回收器(Serial)

JVM第一个垃圾收集器,JDK 1.3.1之前都是有这个收集器。可以作用新生代和老年代。

算法:新生代使用复制算法,老年代使用标记-整理算法

特点:串行单线程、不支持并发、会导致"stop the world"

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//配置如下使用
 -XX:+UseSerialGC

并发垃圾回收器(parNew)

为了解决 serial拉圾收集器引起的停机问题,在serial基础上开发了多线程版本,但是parNew是针对client的版本。

算法:新生代采用复制算法、老年代采用标记-整理算法

特点:多线程、唯一能够与cms搭配使用的收集器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//强制指定使用ParNew
 -XX:+UseParNewGC
//指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同
-XX:ParallerGCThreads

并行垃圾收集器(Parallel Scavenge)

Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 那么它 有什么特别之处呢?Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多 的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞 吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机 去完成也是一个不错的选择。

算法:新生代复制算法,老年代标记-整理算法

特点:Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU),CMS 等垃圾收集器的关注点更多 的是用户线程的停顿时间(提高用户体验)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//注意:启动后不需要手工指定新生代的大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了
-XX:+UseAdaptiveSizePolicy
//使用Parallel收集器+老年代串行 
-XX:+UseParallelGC 
//启用并行压缩 
-XX:+UseParallelOldGC。

并发垃圾回收器 CMS(Concurrent Mark Sweep)

cms主要是解决停顿时间的问题而开发的一种,低停顿、并发收集器,基于标记清除,可以作用于新生代和老年代。

初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

算法:标记-清除

特点:作用于新生代、老年代、低停顿、并发收集

缺点:无法处理浮动垃圾、对CPU资源非常敏感、会造成空间碎片化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:+UseConcMarkSweepGC
//设定CMS的线程数量
-XX:ParallelCMSThreads

G1垃圾回收器

初始标记(它标记了从GC Root开始直接可达的对象);

并发标记(从GC Roots开始对堆中对象进行可达性分析,找出存活对象);

最终标记(标记那些在并发标记阶段发生变化的对象,将被回收);

筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//启用G1垃圾回收器
-XX:+UseG1GC

ZGC

ZGC 收集器是一款JDK 11 中新加入的具有实验性质的低延迟垃圾收集器,与Shenandoah 的目标相同,向往尽可能对吞吐量影响不大的前提下,实现在任意任意堆内存可以把垃圾收集停顿限制在十毫秒之内的低延迟。

  • 不设置分代,使用读屏障、染色指针和内存多重映射实现并发整理算法。
  • 染色指针就是在指向对象的指针上标记对象的一些信息,如可以在指针(即地址)的某些未使用的位置标志对象是否进入重分配集(是否被移动过)、是否只能通过finalize() 方法访问到、引用对象的三色标记状态。
  • 迄今垃圾收集器研究最前沿的成果,几乎整个回收过程都可以并发收集,短暂的停顿(初始标记和最终标记)只与 GC Roots大小相关而与堆内存大小无关。
  • 相较于G1,没有使用写屏障
  • 性能十分高

Shenandoah

前言: Shenandoah 并不是Oracle 官方的收集器,所以 OracleJDK 甚至明确拒绝使用这个高效的垃圾收集器,商用的话可以使用OpenJDK。

该收集器与 G1 的不同有:

  • G1的回收阶段是可以多线程并行的,但是不能与用户线程并行,但是Shenandoah 回收阶段是可以与用户线程并发的,使用了转发指针等技术。
  • Shenandoah 默认不使用分代收集,然而G1 是分新生代和老年代这些的,所以没有专门的新生代和老年代的 Region 。
  • G1 消耗大量的内存去维护记忆集, Shenandoah 改用名为“连接矩阵”的全局数据结构来记录跨Region的引用关系降低了处理跨待指针是的记忆集维护消耗。

垃圾收集器特点对比

收集器

类型

区域

算法

特性

场景

Seial

串行

新生代

复制算法

响应速度优化

单CPU环境下的Client模式

Serial Old

串行

老年代

标记-整理

响应速度优化

单CPU环境下的Client模式、CMS的后备预案

ParNew

并行

新生代

复制算法

响应速度优先

多CPU环境时在Server模式下与CMS配合

Parallel Scavenge

并行

新生代

复制算法

吞吐量优先

后台运算不需要太多交互任务

Parallel Scavenge old

并行

老年代

标记整理

吞吐量优先

后台运算不需要太多交互任务

CMS

并发

老年代

标记清除

响应速度优先

主要用于互联网站或B/S系统服务端上的Java应用

G1

并发

新生代/老年代

标记-整理+复制算法

响应速度优先

面向服务端应用,将来替换CMS

垃圾收集器分类

查看jdk所用内存垃圾回收器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
java -XX:+PrintCommandLineFlags -version

#oracle_jdk(java TM) 看不到使用了什么GC,但是可以从官网文档中看到使用的是Parallel Collector
#https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#parallel_collector_generations

#open_jdk 可以看到使用了什么GC,-XX:+UseParallelGC

三色标记算法

并发标记,就是程序一边运行,一边标记垃圾。只有在并发的GC中才会用到

并发标记一共会有两个问题:一个是错标,标记过不是垃圾的,变成了垃圾(也叫浮动垃圾);第二个是本来已经当做垃圾了,但是又有新的引用指向它。

三色代表的意思

  • 白色:没有检查(或者检查过了,确实没有引用指向它了)
  • 灰色:自身被检查了,成员没被检查完(可以认为访问到了,但是正在被检查,就是图的遍历里那些在队列中的节点)
  • 黑色:自身和成员都被检查完了

三色算法逻辑

假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:

  1. 初始时,所有对象都在 【白色集合】中
  2. 将GC Roots 直接引用到的对象 挪到 【灰色集合】中
  3. 从灰色集合中获取对象:
    1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中
    2. 将本对象 挪到 【黑色集合】里面
  4. 重复步骤3,直至【灰色集合】为空时结束
  5. 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收

三色算法第一种问题: 错标

标记过不是垃圾的,变成了垃圾(也叫浮动垃圾),如下图:标记完了E是D的一个引用,也就是说此时E是灰色的,但是D断开的对E的引用。这个浮动垃圾的问题影响不是很大,可能就是暂时的浪费一点内存,它肯定抗不过下一轮GC。

三色算法第二种问题:漏标,或者叫错杀

这个问题是比较致命的,如果错杀了,就会出现运行结果不符合预期的情况。这个是绝对不能发生的。这发生的情况只有一个,就是D是黑色的,E是灰色的,但是D又指向了G,和E断开了指向G。 因为D已经标记了是黑色,但是E断开了引用,所以G就当做了是白色的。这个时候如果不操作的话,就会把G错杀掉。这种问题是必须解决掉的。

三种不同的解决错杀的方法

  • CMS:写屏障 + 增量更新
  • G1:写屏障 + SATB
  • ZGC:读屏障

参考资料

oraclejdk1.8关于jvm的官方文档

如何选择收集器的官方文档

The Parallel Collector官方文档

(82条消息) 图文详解 三色标记算法_水的精神的博客-CSDN博客_三色标记算法

JVM垃圾收集器(GC)有哪些? - 云+社区 - 腾讯云 (tencent.com)

夺命13追

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档