【JVM篇12】:垃圾回收器种类(从分代到并发)

王者杯·14天创作挑战营·第4期 7.9w人浏览 49人参与


垃圾回收器是JVM内存管理的核心组件,它的选择和调优直接影响着Java应用程序的性能表现,尤其是在高并发和低延迟场景下。不同的业务场景需要不同的垃圾收集器才能保证GC性能

就目前主流的HotSpot JVM而言,垃圾收集器主要分为两大类:分代收集器分区收集器

一、分代收集器

分代收集器是基于JVM分代收集理论设计的,它将堆内存划分为新生代和老年代,并针对不同代的特点采用不同的回收算法

1. 新生代垃圾回收器

新生代对象“朝生夕死”的特点使得复制算法成为其理想选择

1.1 Serial收集器

  • 特点
    • 单线程:Serial收集器是最基本、历史最悠久的收集器。它只会使用一条垃圾收集线程进行工作
    • STW(Stop-The-World):在进行垃圾收集时,它必须暂停所有用户线程,直到收集结束
    • 算法:新生代采用复制算法
    • 适用场景:由于其简单高效,且没有线程交互开销,Serial收集器对于运行在Client模式下的虚拟机是一个不错的选择。在单CPU环境下,其专注性和独占性往往有更好的性能表现
  • 参数:通过-XX:+UseSerialGC参数开启

1.2 ParNew收集器

  • 特点
    • 多线程:ParNew收集器是Serial收集器的多线程版本。除了使用多线程,其控制参数、收集算法、回收策略等与Serial收集器完全一样
    • STW:同样会触发STW
    • 算法:新生代采用复制算法
    • 优势:在多CPU环境下,可以充分利用多核优势,更快完成垃圾收集
    • 特殊性:它是目前新生代首选的垃圾回收器之一,因为它是唯一一个能与老年代CMS收集器配合工作的
  • 参数:通过-XX:+UseParNewGC参数开启。默认开启的线程数与CPU数量相同,可通过-XX:ParallelGCThreads设置

1.3 Parallel Scavenge收集器

  • 特点
    • 多线程:与ParNew类似,也是多线程的并行收集器
    • 关注吞吐量:与CMS关注停顿时间不同,Parallel Scavenge收集器更关注系统的吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值)
    • 算法:新生代使用复制算法
    • 自适应调节策略:它提供-XX:+UseAdaptiveSizePolicy参数,开启后JVM会根据当前系统运行情况动态调整新生代大小、Eden与Survivor比例、晋升老年代对象年龄等参数,以达到最佳吞吐量或停顿时间目标。这是它与ParNew的一个重要区别
    • JDK8默认:JDK8默认使用Parallel Scavenge + Parallel Old组合
  • 参数
    • -XX:+UseParallelGC:开启新生代使用此收集器
    • -XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间
    • -XX:GCTimeRatio:设置吞吐量大小,默认99,即GC时间占比不超过1%

2. 老年代垃圾回收器

老年代对象存活率高,不适合复制算法,通常采用标记-清除或标记-整理算法

2.1 Serial Old收集器

  • 特点
    • 单线程:Serial收集器的老年代版本,同样是单线程收集器
    • 算法:使用标记-整理算法
    • 用途
      • 在JDK1.5及之前版本中与Parallel Scavenge收集器搭配使用
      • 作为CMS收集器的后备预案:当CMS出现Concurrent Mode Failure时,会临时启用Serial Old进行Full GC
  • 参数:无特定开启参数,通常随SerialGC或作为CMS后备

2.2 Parallel Old收集器

  • 特点
    • 多线程:Parallel Scavenge收集器的老年代版本,也是多线程收集器
    • 关注吞吐量:与Parallel Scavenge一样,关注系统吞吐量
    • 算法:使用标记-整理算法
    • 用途:与Parallel Scavenge搭配使用,形成“吞吐量优先”的组合,在注重吞吐量和CPU资源敏感的场合优先考虑
  • 参数:通过-XX:+UseParallelOldGC参数开启

2.3 CMS收集器

  • 特点
    • 并发收集、低停顿:CMS是HotSpot虚拟机第一款真正意义上的并发收集器,其目标是获取最短回收停顿时间。它实现了垃圾收集线程与用户线程(基本上)同时工作
    • 算法:基于**“标记-清除”算法**实现
    • 工作流程(四步)
      1. 初始标记:短暂停顿(STW),标记GC Roots直接关联的对象,速度很快
      2. 并发标记:与用户线程并发执行,遍历整个对象图,标记所有可达对象。这是最耗时的阶段,但无需STW
      3. 重新标记:短暂停顿(STW),修正并发标记期间因用户程序运行导致标记变动的对象记录。停顿时间通常比初始标记稍长
      4. 并发清除:与用户线程并发执行,清除已标记为垃圾的对象
    • 缺点(重要)
      1. 对CPU资源敏感:并发阶段会占用CPU资源,可能导致应用程序变慢,总吞吐量降低
      2. 无法处理浮动垃圾:并发清除时,用户线程还在产生新垃圾,这些垃圾无法在当次GC中处理,只能留待下次清理
      3. 内存碎片化:基于“标记-清除”算法,会产生大量不连续的内存碎片,可能导致大对象无法分配而提前触发Full GC
      4. Concurrent Mode Failure:如果预留内存不足,可能导致CMS失败,转而使用Serial Old进行Full GC,造成长时间停顿
    • 状态在JDK9中被标记为弃用(deprecated),并在JDK14中被移除
  • 参数
    • -XX:+UseConcMarkSweepGC:开启CMS
    • -XX:CMSInitiatingOccupancyFraction:设置老年代空间使用率达到多少时触发GC,默认JDK6及以上为92%
    • -XX:+UseCMSCompactAtFullCollection:在Full GC后进行内存碎片整理,但会增加STW时间
    • -XX:CMSFullGCsBeforeCompaction:设置多少次CMS回收后进行一次内存压缩

二、分区收集器

分区收集器不再严格按照分代划分物理内存,而是将堆划分为多个独立的区域,并根据这些区域的特点进行回收

1. G1收集器(默认)

  • 特点
    • JDK9默认:G1在JDK1.7引入,并在JDK9时取代CMS成为默认垃圾收集器
    • 面向服务端:主要针对配备多核处理器及大容量内存的机器,追求在满足GC停顿时间要求的同时具备高吞吐量
    • Region划分:将整个Java堆划分为多个大小相等的独立区域(Region),每个Region都可以是Eden、Survivor或Old区。G1有专门分配大对象的Humongous区
    • 并行与并发:能充分利用多CPU优势缩短STW时间,部分GC动作可并发执行
    • 分代概念保留:虽然内存分区,但仍保留分代的概念,能独立管理整个GC堆
    • 空间整合:从整体看基于标记-整理算法,从局部(Region之间)看基于复制算法。这使得G1不会产生内存碎片
    • 可预测的停顿:G1一大优势是可以建立可预测的停顿时间模型。用户可以指定期望的停顿时间(-XX:MaxGCPauseMillis,默认200ms),G1会根据此目标选择回收价值最大的Region
    • GC模式:G1包含Young GC、Mixed GC和Full GC
      • Young GC:发生在新生代Region的回收
      • Mixed GC:G1特有,回收整个新生代Region以及部分老年代Region。G1通过多次Mixed GC来逐步回收老年代垃圾,以避免Full GC
      • Full GC:当Mixed GC过程中老年代空间不足,或者堆浪费百分比过高时可能触发
  • 工作流程(四步)
    1. 初始标记:短暂停顿(STW),标记从GC Roots直接可达的对象
    2. 并发标记:与应用并发运行,标记所有可达对象
    3. 最终标记先:短暂停顿(STW),处理并发标记阶段结束后残留的引用变更
    4. 筛选回收:根据标记结果,选择回收价值高的Region,复制存活对象到新Region,回收旧区域内存。此阶段包含STW
  • 参数
    • -XX:+UseG1GC:启用G1收集器
    • -XX:G1HeapRegionSize=n:设置Region大小
    • -XX:MaxGCPauseMillis:设置期望的最大停顿时间

2. ZGC收集器

  • 特点
    • 低延迟、大内存:JDK11推出,目标是不超过10ms的停顿时间下,支持TB级内存容量(未来可达16TB)。停顿时间不随堆大小或活跃对象数量增加而增加
    • 并发性高:ZGC在标记、转移和重定位阶段几乎都是并发的,这是其实现低停顿的关键
    • 算法:采用标记-复制算法,但做了重大优化
    • 核心技术
      • 指针染色:在指针中嵌入对象的元数据信息(如是否被移动、存活状态),通过指针颜色即可区分对象状态,无需额外内存访问,加速标记和转移
      • 读屏障:在程序读取对象时插入特殊检查,确保对象访问的正确性。如果对象已被移动,读屏障会返回对象的新地址,从而在并发移动对象时保持内存访问一致性
    • 工作流程:由STW暂停阶段(标记开始、重新映射开始、暂停结束)和并发阶段(并发标记/重新映射、并发引用处理、并发转移准备、并发转移)组成
    • 发展:在Java11中处于试验阶段,Java15正式可用。Java21引入了分代ZGC,进一步缩短停顿时间
  • 参数:通过-XX:+UseZGC启用

三、总结与选择

垃圾收集器区域算法特点关注点JDK默认(特定版本)
Serial新生代/老年代复制/标记-整理单线程,STW,简单高效简单高效Client模式下默认
ParNew新生代复制多线程版Serial,STW,可与CMS配合吞吐量搭配CMS使用
Parallel Scavenge新生代复制多线程,STW,吞吐量优先,支持自适应调节策略吞吐量JDK8默认新生代
Serial Old老年代标记-整理单线程,STW,Serial的老年代版本,作为CMS后备简单高效作为CMS后备
Parallel Old老年代标记-整理多线程,STW,Parallel Scavenge的老年代版本,与Parallel Scavenge搭配实现高吞吐量吞吐量JDK8默认老年代
CMS老年代标记-清除并发收集,低停顿,但有CPU敏感、浮动垃圾、内存碎片缺点,JDK9弃用,JDK14移除低停顿JDK8前广泛使用
G1全堆标记-整理/复制分区,可预测停顿,并行与并发,无内存碎片,JDK9默认可预测停顿JDK9及更高版本默认
ZGC全堆标记-复制极低延迟(毫秒级),不随堆大小增长,高并发,大内存支持(TB级),采用指针染色和读屏障,JDK11引入,JDK15正式可用极低延迟

选择合适的垃圾收集器
没有“最好”的垃圾收集器,只有“最适合”的。选择时需要根据具体的应用场景、对吞吐量和延迟的要求、以及可用的硬件资源来权衡

  • 追求极致吞吐量:考虑Parallel Scavenge + Parallel Old组合
  • 追求低停顿时间(但可接受一定碎片和CPU开销):在JDK8及之前,考虑CMS
  • 大内存、多核,且追求可控的低停顿:G1是当前主流且推荐的选择
  • 追求极低延迟,尤其是在超大堆内存场景:ZGC是未来的趋势,适用于对停顿时间要求极其苛刻的场景
### JVM垃圾回收器种类及其作用 #### 1. **Serial 收集器** `Serial` 是最基础的垃圾收集器之一,主要用于新生垃圾回收。它是单线程工作的,在垃圾回收期间会触发 Stop-The-World (STW),即暂停所有用户线程。这种收集器适用于小型应用程序或嵌入式设备中的客户端模式下运行的虚拟机[^1]。 优点在于其实现简单且高效(针对单线程环境),但在现多核处理器环境中表现不佳。 ```java // 启动 Serial GC 参数 -XX:+UseSerialGC ``` --- #### 2. **ParNew 收集器** `ParNew` 是 `Serial` 的多线程版本,专门用于新生垃圾回收。它同样会在垃圾回收时引发 STW,但通过并行处理多个线程可以显著缩短停顿时间。因此,相比于 `Serial`,它更适配于具有多核 CPU 的服务器端应用[^3]。 虽然提高了效率,但如果堆内存较大,则仍可能出现较长的停顿时间。 ```java // 启动 ParNew GC 参数 -XX:+UseParNewGC ``` --- #### 3. **Parallel Scavenge (吞吐优先)** 这是一种专注于提升系统整体吞吐量的新生垃圾收集器。与 `ParNew` 类似,它也是多线程运作;然而,不同于后者关注最小化停顿时间的设计理念,`Parallel Scavenge` 致力于最大化程序执行的有效时间比例[^4]。 特别适合那些对响应速度要求不高但希望获得更高性能的应用场合,例如后台批量处理任务。 ```java // 启动 Parallel Scavenge GC 参数 -XX:+UseParallelGC ``` --- #### 4. **CMS (Concurrent Mark-Sweep)** `CMS` (Concurrent Mark Sweep)是一个面向老年垃圾收集器,旨在提供较低的延迟体验。其核心策略是尽可能让大部垃圾回收动作与用户的正常业务逻辑并发执行,从而减少因 STW 导致的服务中断现象[^4]。 不过,由于采用了标记-清除算法而非复制算法,容易造成内存碎片问题,进而可能需要依赖额外的 Full GC 来解决问题。 ```java // 启动 CMS GC 参数 -XX:+UseConcMarkSweepGC ``` --- #### 5. **G1 (Garbage First)** 作为一款现化的垃圾收集器,`G1` 将整个 Java 堆划为许多大小相等的小块区域 (`Region`),并通过预测模型来决定何时以及如何进行局部或者全局范围内的垃圾回收活动[^4]。 相比传统的式方法,这种方式不仅有助于更好地平衡各部之间的负载压力,还支持更加精细地设定期望的最大停顿时间目标。 ```java // 启动 G1 GC 参数 -XX:+UseG1GC ``` --- #### 总结表 | 不同垃圾回收器的作用对比 | 名称 | 主要用途 | 特点 | |--------------------|--------------------|------------------------------------------------------------------------------------------| | Serial | 客户端模式 | 单线程、简单高效 | | ParNew | 新生, 多核 | 多线程版 Serial,降低 STW 时间 | | Parallel Scavenge | 高吞吐量需求 | 注重吞吐量优化 | | CMS | 老年, 低延迟 | 减少停顿时间 | | G1 | 综合优化 | 动态调整、区管理 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值