【JVM】7种经典的垃圾收集器

本文参考:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

1. 垃圾收集器概述

image-20230212113322170

图中所示七种垃圾收集器虽然算不上先进技术,但是它们在实践中足够成熟,基本上可以认为是现在未来两、三年内,能够在生产环境上放心使用的所有垃圾收集器了。

这七种垃圾收集器是作用于不同分代的,两个收集器之间若有连线,则说明它们可以搭配使用。垃圾收集器的位置则表示了属于新生代还是老年代收集器。

垃圾收集器根据发展实践可以分为:

  1. 串行
  2. 吞吐量优先
  3. 响应时间优先

2. Serial 收集器

Serial收集器是最基础、历史最悠久的收集器。它是用于新生代的垃圾收集器。

这个收集器是一个单线程工作的收集器,“单线程”不仅仅代表了这个收集器只会使用一个处理器或一个收集线程去进行垃圾收集工作,更重要的是,Serial 收集器在进行垃圾收集的时候必须暂停其他所有工作,直到收集工作完成。

也就是说在用户不可知、不可控的情况下,把用户的正常工作的线程全部停掉,这是一个不能接受的事情。

image-20230212114757514

即使 Serial 收集器是最早出现的垃圾收集器,但是如今HotSpot虚拟机运行在客户端模式下的默认新生代收集器依然是 Serial 收集器。

Serial 收集器的优点很简单,那就是简单高效,在内存资源受限的环境中,它是所有收集器里额外内存消耗最小的,对于单核处理器或核心数较小的处理器来说,Serial 收集器没有线程交互的开销,专心做垃圾收集因此保证了收集效率。


3. ParNew 收集器

ParNew 收集器实际上就是 Serail 收集器的多线程版本,除了同时使用多线程进行垃圾回收之外,其他的任何行为包括所有控制参数、收集算法等都与 Serail 收集器一样,并无太多创新之处

image-20230212115554893

并且除了Serial收集器外,目前只有ParNew收集器可以与CMS收集器配合工作。

CMS是一款在强交互应用中几乎可称为具有时代意义的垃圾收集器,它是真正意义上支持并发的垃圾收集器,首次实现了让垃圾收集器线程在用户线程同时工作,所以,CMS的出现巩固了ParNew的地位

ParNew 收集器在单核心处理器的环境绝对不必Serial收集器更好的效果,甚至存在线程交互的开销。
ParNew收集器通过超线程技术实现的伪双核处理器环境中都不能百分之百超越Serial。

不过随着处理器核心数量的增加,ParNew对于垃圾收集时的系统资源高效利用还是很有好处的,它默认开启的收集线程数和处理器核心线程数相同。


4. Paraller Scavenge 收集器

Paraller Scavenge 收集器是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器。

该垃圾收集器常被称为“吞吐量优先收集器”

并行:并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态

并发:并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Paraller Scavenge 收集器的特点是达到一个可控的吞吐量。

image-20230212133427488

Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的-XX:GCTimeRatio 参数

  1. -XX:MaxGCPauseMillis:参数允许值是一个大于0的毫秒数,收集器会尽力保证内存回收花费时间不超过用户的设定值,**但是这个参数值不建议设置得更小一点,因为垃圾收集停顿时间是牺牲吞吐量和新生代空间为代价的。**系统把新生代调的小一点,垃圾收集速度肯定快一点,但是随之而来的就是频繁的垃圾收集,因此吞吐量也会下来。
  2. -XX:GCTimeRatio:值应该是一个大于0小于100的正数,也就是垃圾收集时间总时间的比率,相当于吞吐量的倒数。比如设置为19,那么就是允许的最大垃圾收集器占用的时间为5%(即1/(1+19));默认为99,允许的最大1%(1/(1+99))为垃圾收集时间

5. Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,同样也是一个单线程收集器,使用标记-整理算法

image-20230212135752822

如果在服务端模式下,它也可能有两种用途:一种是在JDK 5以及之前的版本中与 Parallel Scavenge 收集器搭配使用9,另外一种就是作为 CMS 收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure 时使用。


6. Parller Old收集器

Parller Old收集器是Parller Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。

image-20230212140005545

在Parller Old收集器没有出现之前,Parller Scavenge收集器只能和Serial Old收集器搭配使用,由于Serial Old收集器应用性能上的拖累,并不能是的Parller Scavenge收集器未必能发挥出最好的吞吐量最大化的效果。这组合甚至不如ParNew加CMS的组合来得优秀。

直到 Paraller old 收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的搭配组合,在注重吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑 Paraller Scavenge 加Paraller Old 收集器这个组合。


7. CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的 Java 应用集中在互联网网站或者基于浏览器的 B/S 系统的服务端上,这类应用通常都会较为关注服务的响应速度希望系统停顿时间尽可能短,以给用户带来良好的交互体验。

CMS收集器是基于标记-清除算法实现的。它的回收垃圾的步骤如下

  1. 初始标记

    1. 需要Stop The Word,仅仅标记从GC Roots的直接关联对象,速度很快
  2. 并发标记

    1. 从GC Roots的直接关联对象开始遍历整个对象图的过程,过程耗时但不需要停顿用户线程,可以与垃圾收集线程一起并发允许
  3. 重新标记

    1. 需要Stop The Word,为的是修正并发标记期间,因用户程序继续允许而导致标记产生变动的拿一些对象的标记记录。
    2. 停顿时间比初始标记时间常,但比并发标记时间短
  4. 并发清除

    1. 清除标记阶段判断已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以和用户进程同时运行的

image-20230212140536014

CMS的优点的优点有并发收集、低停顿。

不过CMS并没有达到完美,它有以下几个缺点

  1. CMS收集器对处理器资源非常敏感
  2. CMS无法处理浮动垃圾
    1. 由于并发标记和并发清除阶段,用户线程是继续运行的,程序在运行的时候自然会产生新的垃圾对象。
  3. 由于使用标记-清除算法实现,所以会导致空间碎片比较多。

8. Garbage First 收集器

Garbage First 收集器也成为G1收集器,它是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。

G1收集器面向堆内存任何部分来组成回收集 (Collection Set,一般简称 CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是 G1 收集器的 Mixed GC 模式。

G1开创的基于 Region 的堆内存布局是它能够实现这个目标的关键。虽然 G1 也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间或者老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

Region 是 G1收集器的单位回收的最小单位,这样做可以有计划避免整个JAVA堆中进行全区域的垃圾收集。并且G1收集器会跟踪各个Region的价值大小,也就是回收所获得的空间大小以及回收所需要的时间的经验值,在后台形成一个优先队列,再根据用户设定的允许的停顿时间,优先处理那些回收价值大的Region,这样保证了G1收集器在有限的时间内获取尽可能高的收集效率。

image-20230212161608985

G1收集器的运作步骤

  1. 初始标记
    1. 仅仅只是标记一下GC Roots 能直接关联到的对象并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC 的时候同步完成的,所以G1 收集器在这个阶段实际并没有额外的停顿。
  2. 并发标记
    1. 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理 SATB 记录下的在并发时有引用变动的对象。
  3. 最终标记
    1. 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录。
  4. 筛选回收
    1. 负责更新 Region 的统计数据,对各个Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

image-20230212162540809


猜你喜欢

转载自blog.csdn.net/weixin_51146329/article/details/128996486