61、垃圾回收器(上)
上一节我们讲了分代垃圾回收算法,针对不同的分代使用不同的基础垃圾回收算法,比如:标记 - 清除、标记 - 整理、标记 - 复制算法
本节我们讲解垃圾回收算法的具体实现,也就是垃圾回收器,同一种垃圾回收算法,可以有不同的实现方式,对应不同的垃圾回收器
垃圾回收器有很多,在平时的项目开发中,我们如何从众多垃圾回收器中,为项目选择合适的垃圾回收器呢?带着这个问题,我们来学习本节的内容
1、垃圾回收器的性能指标
不同垃圾回收器的性能特点不同,应用场景也不同,一般来讲,我们从以下几个指标来评价一个垃圾回收器
- 吞吐量:业务代码运行时间占应用程序总运行时间的比值,应用程序总运行时间 = 业务代码运行时间 + 垃圾回收时间(简称 GC 时间)
吞吐量 95% 表示在应用程序总运行时间中,业务代码运行时间占比 95%,GC 时间占比 5%(吞吐量越大,表示浪费在 GC 上的时间越少) - 停顿时间:垃圾回收导致应用程序完全停止业务执行的时间,也就是之前讲到的 STW(Stop The World)时间
- 资源消耗:CPU、内存
Parallel 垃圾回收器使用多线程进行垃圾回收,占用的 CPU 资源要比 Serial 垃圾回收器多,CMS 垃圾回收器的停顿时间虽然比较短,但需要预留内存
在一些资料中,有人还会将回收延迟和回收频率作为垃圾回收器的评价指标
- 回收延迟:从一个对象死亡到被回收所经历的等待时间
- 回收频率:隔多久进行一次垃圾回收
实际上这两者跟垃圾回收器本身没有太大关系,而是跟虚拟机的垃圾回收的整体策略(比如什么情况下触发垃圾回收),以及堆和堆中各个分代大小的设置有关
2、四大常用垃圾回收器
为了满足不同的应用场景,虚拟机提供了很多不同的垃圾回收器
我们按照实现方式(串行还是并行、是否 Stop The World、针对年轻代还是老年代等)将众多垃圾回收器分为 4 类
- Serial 垃圾回收器(Serial Garbage Collector 串行垃圾回收器)
- Parallel 垃圾回收器(Parallel Garbage Collector 并行垃圾回收器)
- CMS 垃圾回收器(Concurrent Mark Sweep Garbage Collector 并发标记清除垃圾回收器)
- G1 垃圾回收器(Garbage First 垃圾回收器)
接下来我们简单介绍一下各个垃圾回收器,这里特别说明一下,我们并不会深入的详细讲解各个垃圾回收器的具体实现方式
- 它们比较复杂
- 了解它们对实际项目开发并没有太大帮助
我们只需要对各个垃圾回收器的性能特点有一定了解,就可以做到根据不同的项目需求选择合适的垃圾回收器
在上一节中我们讲到,永久代也会存在垃圾回收,因为永久代一般使用老年代相同的垃圾回收器进行垃圾回收,所以在本节的讲解中,我们并未提及永久代
2.1、Serial 垃圾回收器
Serial 垃圾回收器使用单线程进行垃圾回收,并且在进行垃圾回收时,虚拟机需要暂停应用程序的运行(也就是 Stop The World)
针对所工作的分区的不同,Serial 垃圾回收器又分为 Serial New 和 Serial Old
- Serial New 用于年轻代的垃圾回收,基于标记 - 复制算法来实现
- Serial Old 用于老年代的垃圾回收,基于标记 - 整理算法来实现
2.2、Parallel 垃圾回收器
Parallel 垃圾回收器使用多线程进行垃圾回收,可以充分利用 CPU 资源,跟 Serial 垃圾回收器相同,Parallel 垃圾回收器在进行垃圾回收时,也需要暂停应用程序的运行
Parallel 垃圾回收器包含 Parallel Scavenge(简称为 PS)、Parallel New(简称为 ParNew)、Parallel Old(简称为 ParOld)三个细分的垃圾回收器
- Parallel Scavenge 和 Parallel New 用于年轻代的垃圾回收,基于标记 - 复制算法来实现
- Parallel Old 用于老年代的垃圾回收,基于标记 - 整理算法来实现
- Parallel Scavenge 跟 Parallel Old 配合使用,Parallel New 跟 CMS 配合使用
2.3、CMS 垃圾回收器
CMS 全称为 Concurrent Mark Sweep,CMS 垃圾回收器采用多线程执行垃圾回收
- 跟 Parallel 垃圾回收器所不同的是,CMS 垃圾回收器在进行垃圾回收时,并不需要完全暂停应用程序,因此 STW 时间更短
- 需要注意的是,CMS 不能用于年轻代的垃圾回收,如果我们选择使用 CMS 垃圾回收器,那么年轻代将默认使用 Parallel New 垃圾回收器
如果我们设置 JVM 参数 -XX:-UseParNewGC,那么年轻代将改用 Serial New 垃圾回收器
CMS 垃圾回收器将整个垃圾回收过程分为 4 个阶段:初始标记、并发标记、重新标记、并发清理
- 初始标记和重新标记需要暂停应用程序
- 并发标记和并发清理可以与应用程序并发执行
对于 CMS 如何做到并发标记、并发清理的,我们在下一节中详细讲解
CMS 垃圾回收器在与应用程序并发执行的过程中会争抢 CPU 资源,因此默认情况下,CMS 使用的并发线程数 = (CPU 内核数 + 3) / 4
除此之外,CMS 垃圾回收器在与应用程序并发执行的过程中会争抢内存资源
因此老年代在快要满但没有满的时候,虚拟机就要进行垃圾回收,这样做的目的是为并发执行的应用程序预留内存空间
那么到底老年代中已使用内存占比多少时,虚拟机就会进行垃圾回收呢?这是由虚拟机根据动态计算得到的
我们也可以通过 JVM 参数 -XX:CMSInitiatingOccupancyFraction 来指定这种比例值
比如:设置 -XX:CMSInitialOccupancyFraction=80,那么当已使用内存占老年代的 80% 时,虚拟机便会触发 CMS 垃圾回收器的执行
不过即便设置再合理的比例值,预留空间总会有不够用的时候,此时虚拟机将中止 CMS 垃圾回收器的执行,转而使用 Serial Old 垃圾回收器进行本次的垃圾回收
除此之外,为了进一步减少 STW 时间,CMS 采用标记 - 清除算法来实现,相对于标记 - 整理算法,省去了整理空闲空间的时间
标记 - 清除算法存在内存碎片问题,影响对象的内存分配效率,为了解决这个问题,CMS 垃圾回收器对此进行了改进,会在多次进行垃圾回收之后,紧跟着进行一次内存碎片的整理
2.4、G1 垃圾回收器
G1 全称叫做 Garbage First,G1 垃圾回收器是一个应用于整个堆上的垃圾回收器,它的实现方式跟其他垃圾回收器有较大差别
上一节中我们讲到分代垃圾回收算法,针对不同的分代采取不同的回收策略
分代垃圾回收避免了每次都要对整个堆进行垃圾回收,缩短了垃圾回收的时间,进而缩短了 STW 时间
借鉴分代的处理思路,G1 垃圾回收器将整个堆划分为很多(一般是 2048 个)小的区域(Region)
部分区域划分为年轻代(Eden 区或 Survivor区),部分区域划分为老年代,如下所示各个分代中的内存并不是连续的
之前的垃圾回收器都是针对整个分代进行垃圾回收,要么是整个年轻代,要么是整个老年代,因为要回收的内存空间比较大,所以垃圾回收时间也比较长
当年轻代和老年代划分为更多更小区域之后,每次进行垃圾回收时,虚拟机可以只回收分代中的部分区域,进一步缩短了 STW 时间
当然 G1 垃圾回收器跟 CMS 垃圾回收器类似,也是多线程进行垃圾回收的,并且回收的过程可以跟应用程序并发执行
在 G1 垃圾回收器中,年轻代的垃圾回收使用标记 - 复制算法,老年代的垃圾回收使用标记 - 清除算法
跟其他垃圾回收器相比,G1 垃圾回收器的 STW 时间是可预期的,我们可以通过 JVM 参数 -XX:MaxGCPauseMillis 设置可允许的最大 STW 时间
G1 垃圾回收器会根据这个时间,来决定每次对多少个区域进行垃圾回收,理论上讲 STW 时间设置的越小,每次进行垃圾回收的区域就越少,垃圾回收的频率就越高
3、垃圾回收器的对比与选择
以上介绍了 4 类常用的垃圾回收器,在默认情况下,Java 7、Java 8 均采用 Parallel 垃圾回收器,Java 9 采用 G1 垃圾回收器
我们也可以通过设置 JVM 参数,来指定项目所使用的垃圾回收器,具体如下所示
垃圾回收器 | JVM 参数设置 | 年轻代 | 老年代 |
---|---|---|---|
Serial | -XX:+UseSerialGC | Serial New | Serial Old |
Parallel | -XX:+UseParallelGC | Parallel Scavenge | Parallel Old |
CMS | -XX:+UseConcMarkSweepGC | Parallel New | CMS |
G1 | -XX:+UseG1GC | G1 | G1 |
在实际的项目中,我们该如何选择使用哪种垃圾回收器呢?
- Serial 垃圾回收器使用单线程进行垃圾回收,在多核系统下无法发挥多核的优势
但是在单核系统下,因为其实现简单,相对于 Parallel 垃圾回收器,省去了线程切换的开销,执行更加高效
因此 Serial 垃圾回收器常用于单核系统、多个应用程序争用 CPU 资源的环境下、需要刻意限制虚拟机所占用资源的环境下
比如运行在移动端的客户端程序一般会采用 Serial 垃圾回收器,这类程序所占用内存空间往往比较小,垃圾回收不会耗时很多,因此 Serial 垃圾回收器足够应付 - Parallel 垃圾回收器跟 CMS 垃圾回收器相比,前者吞吐量更大,后者停顿时间更少
对于离线服务:我们首选吞吐量大的 Parallel 垃圾回收器
对于实时服务,特别是对响应时间比较敏感的服务:我们首选停顿时间更少的 CMS 垃圾回收器 - 不过在 Java 9 中,CMS 垃圾回收器被标记为 Deprecated,即可以使用但不推荐使用,取而代之的是 G1 垃圾回收器
特别是对于比较大(大于 6 GB)的堆,我们应该首选停顿时间可控的 G1 垃圾回收器
4、课后思考题
为什么说 G1 比起 CMS 等垃圾回收器更加适合比较大的堆的垃圾回收呢?
G1 垃圾回收器的 STW 时间可控,避免因堆太大而导致的 FullGC 时间过长
本文来自博客园,作者:lidongdongdong~,转载请注明原文链接:https://www.cnblogs.com/lidong422339/p/17498614.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步