JVM面试(六)垃圾收集器

目录

概述

上一章我们分析了垃圾收集算法,那这一章我们来认识一下这些垃圾收集器是如何运用这些算法的。

不同的虚拟机也会提供各种参数,让用户自由的选择使用哪些分代收集器。

如图:

这里有七种不同的分代收集器,存在连线的证明可以搭配组合使用。 上面的Young部分是新生代收集器,下面的Tenured是老年队收集器。 接下来分析一下各个收集器的运行原理。

STW

了解之前这些收集器之前,我们要先了解一个概念,叫做"Stop the World",简称STW,就是在运行垃圾收集算法的时候,Java虚拟机中其他所有的用户线程都要停止。 等到GC线程运行完成之后,才会再次运行。

那么对于一些运算实时性要求较高的系统,如果每隔一个小时就要停止几分钟,肯定是受不了的吧。

所以,这些年垃圾收集器不断进化的过程中,也在不断地减少STW的时间,尽量少的影响用户线程。

收集器的并发和并行

从ParNew收集器之后,将会接触到若干款涉及"并发"和"并行"概念的收集器。有必要先解释清楚这两个名词。并行和并发都是并发编程中的专业名词,

在谈论垃圾收集器的上下文语境中,它们可以理解为:

  • 并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。
  • 并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾
    收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Serial收集器

这个收集器是最基础、历史最悠久的收集器。 只看名字就可以知道,它是一个单线程的收集器。但是这个单线程并不是仅仅说只会用一个收集线程去完成垃圾收集工作,重要的是在运行的时候必须要暂停其他所有的用户线程,只有这一个GC线程运行,直到它收集完成。

如图:

看起来这个像是一个老掉牙的收集器,但是直到今天其实HotSpot虚拟机在客户端模式下,新生代还是用的这个收集器。

因为它虽然是单线程,但是简单高效,在内存资源较少的环境中,它是额外消耗最少得收集器。像一些用户的桌面应用,新生代使用的内存本来就只有几百兆,只要垃圾收集的停顿时间控制在几十毫秒,不是频繁发生收集的话,完全是可以接收的。

所以,Serial收集器对于运行在客户端模式下的虚拟机来说,是也是一个性价比挺高的选择。

ParNew收集器

ParNew收集器实际上就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集,以减少STW时间之外,其他的配置项控制参数之类,与Serial收集器完全一致。

这里要说一下,其实ParNew收集器没有什么创新,但是为什么还会作为服务端HotSpot虚拟机,首选的新生代收集器,是因为除了Serial收集器,只有他能与后来的CMS收集器搭配使用。

而CMS收集器是划时代的,真正意义上可以与用户线程并发处理的垃圾收集器,下文详细讲解一下。

回过来继续说ParNew收集器,虽然他在收集阶段实现了多线程收集,减少了STW时间。但是如果在一些单核处理器的环境中,并没有比Serial收集器有更好的表现。

甚至因为线程交互,上下文切换的开销,他的效果还不能百分百超过Serial收集器。

但是,如果处理器的核心数量比较多的话,ParNew收集器的表现还是挺不错的。

Parallel Scavenge收集器

Parallel Scavenge收集器也是新生代收集器,同样是基于标记-复制算法实现的收集器,能够并行收集的多线程处理器。

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

这里的 吞吐量,就是处理用户线程时间,和 总时间的比值。

比如 用户代码+垃圾收集总时间100分钟,其中垃圾回收用了1分钟,那么吞吐量就是99%。

这个吞吐量越高,垃圾收集的停顿时间就越短。收集时间短的话,收集垃圾对象较少,可能发生 的就更频繁。

两个参数搭配使用,我们可以在其中找一个平衡点,尽量减少停顿时间。

Serial Old收集器

就是Serial新生代收集器的老年代版本,同样也是一个单线程收集器,用标记-整理算法来进行收集。

Parallel Old收集器

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

现。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。(也就是指Stop the World的停顿时间)多数应用于互联网站或者B/S系统的服务器端上。其中"Concurrent"并发是指垃圾收集的线程和用户执行的线程是可以同时执行的。

从名字(包含"Mark Sweep")上就可以看出CMS收集器是基于标记-清除算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为四个步骤,包括:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

但其实后面还有三个步骤:

  • 并发预清理 CMS-concurrent-preclean-start
  • 并发可中断预清理 concurrent-abortable-preclean
  • 并发重置 CMS Final Remark

注意:"标记"是指将存活的对象和要回收的对象都给标记出来,而"清除"是指清除掉将要回收的对象。

其中,初始标记、重新标记这两个步骤仍然需要"Stop The World"。

  1. 初始标记只是标记⼀下GC Roots能直接关联到的对象,速度很快。
  2. 并发标记阶段就是进行GC Roots Tracing的过程。(其实就是从GC Roots开始找到它能引用的所有其它对象,不会影响用户的线程)
  3. 重新标记阶段则是为了修正并发标记期间因用户程序继续动作而导致标记产生变动的那⼀部分对象的标记记录,这个阶段的停顿时间⼀般会比初始标记阶段稍长⼀些,但远比并发标记的时间短。
  4. CMS收集器的动作步骤如图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与⽤户线程⼀起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程⼀起并发执行的;

优点就是可以并发收集、低停顿时间。降低GC线程在清理垃圾的过程中对用户线程的暂停时间。

缺点呢,就是标记清除算法回收完垃圾之后,没有整理内存,会产生大量的内存碎片。

还有就是比较消耗CPU资源,因为是并发多线程的,所以如果处理器核心数量低于4个的话,执行速度肯定会大幅度下降的。

注意:如果CMS垃圾收集器回收的时候,预留的内存无法满足程序分配的新对象需要。那么就会出现并发失败的报错"Concurrent Mode Failure"。

这个时候会触发临时方案,转而使用Serial Old收集器来进行处理,那么这种串行收集器的停顿时间肯定会偏长。

PS:这里还有一个FullGC 的概念,就是新生代、老年代同时发生垃圾回收。 当大对象在新生代放不下,只能进入老年代的时候,或者是新生代对象晋升到老年代的时候。如果进行了Old GC过程中依然发现空间不足无法放下的时候,就会进行Full GC。

Garbage First(G1)收集器

简称G1收集器,它属于里程碑式的发展,开创了面向局部收集垃圾的概念。专门针对多核处理器以及大内存的机器。在JDK9中,更是呗指定为官方的GC收集器。满足高吞吐的通知满足GC的STW停顿时间尽可能的短。

  • 它把堆内存分割为很多不相关的区域(Region) (物理上不连续的)。使用不同的Region来表示Eden、幸存者(S0)区,幸存者(S1)1区,老年代等。
  • G1 GC有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1跟踪各个Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护⼀个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

优点就是可以同时多个GC线程同时执行,在不同的Region中分割处理,并且其他的Region中用户的线程是正常执行。

而且,G1也是有新生代和老年代的概念,但也是标记不同的Region来实现,也就是说 新生代和老年代的位置并不是固定的。

而且,当 内存不足时候,可以通过算法预测清理哪些Region是最高效的,这样STW停顿时间就在一个可控的范围内。

缺点就是只适用于多核处理器和大内存主机。

我们后面可以单开文章,来详细剖析一下CMS收集器和G1收集器具体的实现逻辑。

相关推荐
雾月558 分钟前
LeetCode 1292 元素和小于等于阈值的正方形的最大边长
java·数据结构·算法·leetcode·职场和发展
24k小善1 小时前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者1 小时前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
天天扭码1 小时前
总所周知,JavaScript中有很多函数定义方式,如何“因地制宜”?(ˉ﹃ˉ)
前端·javascript·面试
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
猿周LV2 小时前
JMeter 安装及使用 [软件测试工具]
java·测试工具·jmeter·单元测试·压力测试
晨集2 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城2 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
椰羊~王小美2 小时前
LeetCode -- Flora -- edit 2025-04-25
java·开发语言
凯酱2 小时前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis