深度拆解JVM垃圾回收:可达性分析原理+全类型回收器执行机制

一、根基:可达性分析的具体原理机制

GC的核心前提是"精准识别垃圾"------判断哪些对象已不再被使用。早期"引用计数法"因循环引用缺陷被淘汰,HotSpot虚拟机采用的可达性分析算法,是当前最成熟、通用的垃圾判定方案。

1. 核心思想

以"GC Roots"为起点构建对象引用关系图,遍历所有可达对象并标记为"存活",未被标记的对象则判定为垃圾等待回收。类比来看,GC Roots如同"树根",可达对象是延伸的"树枝",垃圾便是脱离主干的"枯叶"。

2. GC Roots的具体分类(面试高频)

并非所有对象都能作为GC Roots,仅"绝对不会被回收"的核心引用可担当,具体分为4类:

  • 虚拟机栈引用对象:栈帧中局部变量表、操作数栈引用的对象(如方法内定义的局部变量);
  • 方法区静态属性引用对象 :类的static变量引用的对象(例:private static User user = new User());
  • 方法区常量引用对象:字符串常量池、Class常量引用的对象;
  • 本地方法栈引用对象:Native方法(如JNI调用)引用的对象。

3. 具体实现、核心组件与回收器差异

可达性分析的高效执行依赖HotSpot底层机制支撑,不同回收器为适配性能目标,在实现细节、组件选型上差异显著。以下从核心流程、关键组件、回收器差异三方面拆解,兼顾原理与实战。

(1)可达性分析的核心实现流程

完整流程分为"根枚举-对象图遍历-并发修正"三步,每一步均经针对性优化,平衡准确性与执行效率:

  1. 根枚举:基于OopMap的快速定位
    根枚举的核心是高效找到所有GC Roots,HotSpot通过`OopMap(普通对象指针映射表)`规避全栈遍历的低效问题。在方法JIT编译、类加载阶段,虚拟机自动记录栈帧中引用类型数据的内存偏移量并生成OopMap。初始标记阶段(STW),无需遍历整个虚拟机栈/本地方法栈,仅扫描OopMap记录位置即可快速定位根引用对象,此阶段耗时可压缩至毫秒级。同时,方法区的静态属性、常量引用,通过遍历元数据(Class对象静态变量表、常量池)补充,结合OopMap完成全量GC Roots枚举。
  2. 对象图遍历:分层优化减少扫描范围
    从根引用出发,通过对象头引用指针递归遍历可达对象,标记对象头Mark Word中的"存活标记位"。为避免全堆扫描,分代场景用卡表处理跨代引用,分区场景用记忆集处理跨Region引用,仅针对性扫描关联区域,大幅降低遍历开销。
  3. 并发修正:解决并发标记漏标问题
    现代回收器(G1/ZGC)为减少STW时间采用并发标记,需通过特殊机制修正应用线程修改引用导致的漏标,不同回收器的解决方案差异明显,下文将结合回收器特性详细说明。

(2)卡表(Card Table)的具体实现

卡表是分代回收场景中处理"老年代→年轻代"跨代引用的核心组件,本质是"空间换时间",避免跨代遍历全堆,其底层细节直接影响回收效率:

  1. 核心结构 卡表是一块连续内存区域,按512字节粒度将老年代划分为若干"卡页",每个卡页对应卡表中1字节标记位。标记位分"干净"(无跨代引用)和"肮脏"(存在老年代→年轻代引用)两种状态,通过位运算实现快速更新与读取。
  2. 标记触发机制
    当老年代对象引用年轻代对象时,虚拟机通过**写屏障(Write Barrier)**自动标记对应卡页为"肮脏"。写屏障是插入在对象引用赋值操作后的轻量代码,无需开发者干预,仅增加微小开销即可精准追踪跨代引用变更。
  3. 扫描与清理逻辑
    年轻代GC时,无需遍历整个老年代,仅扫描卡表中"肮脏"卡页,逐个检查页内老年代对象是否引用年轻代存活对象,验证后将卡页标记回"干净",显著减少扫描范围。

(3)不同回收器的实现差异对比

各回收器因性能目标(吞吐量/低延迟)、内存管理模式(分代/分区)不同,在可达性分析实现、跨引用处理组件上差异显著,直接决定其适用场景:

回收器类型 可达性分析核心实现 跨引用处理组件 并发漏标解决方案
Serial GC(串行) 单线程执行,OopMap根枚举+全堆遍历,全程STW无并发阶段 卡表(分代场景),逻辑简单适配年轻代标记-复制 无并发阶段,无需漏标修正
Parallel GC(并行吞吐量优先) 多线程并行根枚举+遍历,STW时间短于Serial,依赖OopMap优化 卡表(分代场景),多线程并行扫描肮脏卡页提效 无并发阶段,无需漏标修正
CMS GC(并发低延迟) 初始/重新标记(STW+OopMap)+ 并发标记,减少STW占比 卡表(分代场景),写屏障标记肮脏页,重新标记阶段扫描 增量标记+重新标记,修正并发漏标对象
G1 GC(分区均衡型) 初始/最终标记(STW+OopMap)+ 并发标记,按Region分区遍历 Remembered Set(记忆集)+ 卡表,每Region维护跨Region引用 SATB(快照原子化),记录并发引用变更,最终标记批量修正
ZGC(超低延迟大内存) 初始/最终标记(STW+颜色指针)+ 全阶段并发遍历,无全堆扫描 无卡表/记忆集,依赖颜色指针+读屏障追踪跨Region引用 读屏障拦截引用访问,自动标记漏标对象,无需快照

补充:Shenandoah GC与ZGC目标一致(超低延迟),但用软件层面转发指针替代颜色指针,跨引用处理依赖连接矩阵,无需硬件支持,兼容性更强,小堆(4-16GB)场景性能更优。

二、核心:三大基础垃圾回收算法

所有回收器的底层均基于以下三种算法或其组合优化,算法的取舍直接决定回收器的性能特性,也是适配不同场景的核心依据。

1. 标记-清除算法(Mark-Sweep)

最基础的GC算法,分为"标记"与"清除"两个核心阶段:

  1. 标记:通过可达性分析标记所有存活对象;
  2. 清除:遍历内存区域,释放未标记的垃圾对象,将空闲内存加入链表管理。

优缺点:实现简单、内存利用率高,但会产生大量内存碎片(可能导致大对象无法分配),且清除阶段需遍历全堆,效率随堆大小增长而下降,稳定性差。

适用场景:早期Serial GC老年代、CMS老年代(核心算法)。

2. 标记-复制算法(Mark-Copy)

为解决内存碎片问题设计,核心思路是"空间换时间",适配对象存活率低的场景:

  1. 分区:将内存划分为两个(或多个)等大区域,仅使用其中一个(From区);
  2. 标记复制:通过可达性分析标记存活对象,按顺序复制到空闲区域(To区),天然避免碎片;
  3. 切换:互换From区与To区角色,清空原From区供下次分配使用。

优缺点:无内存碎片、回收效率高,但内存利用率低(需预留空闲区),当对象存活率高时,复制开销会显著增加。

适用场景:年轻代(对象朝生夕死,存活率低),如Serial、Parallel、ParNew等回收器的年轻代。

3. 标记-整理算法(Mark-Compact)

兼顾内存连续性与利用率,是标记-清除算法的优化版,适配对象存活率高的场景:

  1. 标记:通过可达性分析标记所有存活对象(与前两种算法一致);
  2. 整理:将所有存活对象向内存一端移动,集中形成连续空闲空间;
  3. 清除:释放存活对象末尾的空闲内存,统一管理供后续分配。

优缺点:无内存碎片、内存利用率高,但对象移动需更新引用指针,带来额外开销,执行效率略低于标记-复制。

适用场景:老年代(对象存活率高),如Serial Old、Parallel Old回收器。

三、实战:全类型JVM垃圾回收器解析

JVM垃圾回收器可分为"经典分代回收器"(基于分代假说)与"新一代回收器"(面向低延迟、大内存),不同回收器的算法选型、执行流程适配不同场景,需结合业务需求选择。

(一)经典分代回收器(JDK 8及之前主流)

基于"分代假说"(对象朝生夕死、存活越久越难回收),将堆分为年轻代(Eden+Survivor)与老年代,针对性采用算法,平衡回收效率与内存利用率。

1. Serial GC(串行回收器)

核心特性:单线程执行回收,全程STW,实现最简单,无线程交互开销,适合资源受限场景。

算法选型

  • 年轻代:标记-复制算法(Eden:Survivor=8:1,仅用10%内存作为空闲区,提升利用率);
  • 老年代:标记-整理算法。

执行过程

  1. 年轻代回收:Eden区满时触发,复制存活对象到Survivor区并将对象年龄+1,年龄达标(默认15)则晋升老年代,最终清空Eden与原Survivor区;
  2. 老年代回收:老年代空间不足时触发,标记存活对象并整理至内存一端,释放空闲空间供后续分配。

适用场景:单核CPU、客户端应用、小型服务(对停顿不敏感),JDK 1.3前为默认回收器。

2. Parallel GC(并行回收器,吞吐量优先)

核心特性:多线程并行回收,目标是最大化吞吐量(应用运行时间/总时间),JDK 8默认回收器,适配后台运算场景。

算法选型

  • 年轻代:并行标记-复制(多线程同时执行标记与复制,加速回收);
  • 老年代:并行标记-整理(多线程并行标记+整理,缩短STW时间)。

执行过程:与Serial GC核心流程一致,仅将单线程操作改为多线程并行,多核环境下STW时间显著缩短,吞吐量大幅提升。

适用场景:后台运算、科学计算、大数据处理(对吞吐量要求高,可容忍百毫秒级停顿)。

3. ParNew GC(并行年轻代回收器)

核心特性:Serial GC的多线程版本,仅负责年轻代回收,需与CMS老年代回收器搭配使用,是JDK 5-8低延迟场景的过渡方案。

算法选型:年轻代并行标记-复制(与Parallel GC年轻代逻辑一致)。

执行过程:同Parallel GC年轻代回收,仅作为CMS的"年轻代搭档"分担回收压力,无独立老年代回收逻辑。

局限性:JDK 9标记为废弃,JDK 14正式移除,被G1回收器全面替代。

4. CMS GC(并发标记-清除回收器,低延迟优先)

核心特性:以最短停顿时间为目标,老年代回收大部分工作与应用线程并发执行,是JDK 5-8低延迟场景的首选。

算法选型

  • 年轻代:搭配ParNew,采用并行标记-复制;
  • 老年代:并发标记-清除(核心创新,大幅减少STW时间)。

执行过程(老年代核心流程)

  1. 初始标记(STW):标记GC Roots直接关联对象,耗时极短(毫秒级);
  2. 并发标记:GC线程与应用线程并行,遍历对象图标记存活对象,无STW;
  3. 重新标记(STW):修正并发标记期间因应用线程修改引用导致的漏标,耗时短于初始标记;
  4. 并发清除:GC线程与应用线程并行,清除未标记垃圾对象,无STW。

优缺点:停顿时间短,但会产生内存碎片、占用CPU资源(并发阶段影响吞吐量),无法处理浮动垃圾(并发清除阶段产生的新垃圾,需下次GC回收)。

局限性:JDK 9废弃,JDK 14移除,被G1/ZGC替代。

(二)新一代回收器(JDK 9+主流,低延迟、大内存)

打破分代回收的严格边界,采用Region分区管理堆内存,兼顾吞吐量与低延迟,支持TB级堆内存,适配现代服务端场景。

1. G1 GC(Garbage-First,区域优先)

核心特性:JDK 9+默认回收器,面向服务端应用,通过"优先回收垃圾最多的Region"实现可控停顿时间,平衡延迟与吞吐量。

核心设计:将堆划分为多个大小相等的Region(1MB~32MB,自动计算),每个Region可动态作为Eden、Survivor、Old区,支持混合回收(年轻代+部分老年代)。

算法选型:整体标记-整理,局部标记-复制(Region间复制存活对象,避免碎片)。

执行过程(混合回收核心流程)

  1. 初始标记(STW):依附于Young GC执行,标记GC Roots直接关联对象,同时通过卡表定位老年代对年轻代的引用;
  2. 并发标记:遍历对象图标记存活对象,通过写屏障维护Remembered Set(记录跨Region引用);
  3. 最终标记(STW):处理SATB机制记录的漏标对象,修正标记结果;
  4. 筛选回收(部分STW):统计各Region回收收益(释放空间/耗时),优先选择高收益Region,复制存活对象到空闲Region并清空原Region;
  5. 混合回收:分批次回收年轻代+部分老年代Region,通过-XX:MaxGCPauseMillis(默认200ms)控制停顿时间。

适用场景:中型到大型堆内存(4GB~64GB),对停顿时间有要求的服务端应用(如电商、金融核心服务)。

2. ZGC(Z Garbage Collector,超低延迟)

核心特性:Oracle推出的新一代回收器,目标是将停顿时间控制在10ms以内,支持TB级堆内存,全阶段并发(仅初始/最终标记短暂STW)。

核心创新:采用颜色指针(硬件级支持)和读屏障,替代传统写屏障,并发开销仅为写屏障的1/5,大幅提升并发效率。

算法选型:并发标记+并发整理(无碎片,适配大内存场景)。

执行过程

  1. 初始标记(STW,1-2ms):标记GC Roots直接引用对象,标记为灰色;
  2. 并发标记:GC线程与应用线程并行,按颜色指针流转标记(灰→黑,白→灰),读屏障自动标记被引用的白色对象,避免漏标;
  3. 并发整理:选择垃圾占比超25%的Region,复制存活对象到新Region,通过颜色指针重定向引用;
  4. 最终标记(STW,1ms):确认引用变更,重置颜色指针状态;
  5. 并发清理:释放无存活对象的Region,更新元数据供后续分配。

适用场景:大内存(16GB+)、超低延迟场景(如实时数据处理、云原生服务、高频交易系统)。

3. Shenandoah GC(红帽版低延迟回收器)

核心特性:与ZGC目标一致(超低延迟),但采用软件层面转发指针替代颜色指针,无需硬件支持,兼容性更强,支持分代优化。

核心创新:转发指针(对象头存储新地址,实现引用重定向)、连接矩阵(优化跨Region引用追踪,降低遍历开销)。

算法选型:并发标记+并发整理。

与ZGC差异:弱化硬件依赖,小堆(4-16GB)场景性能更优,分代管理更完善,适合混合工作负载(既有短期对象也有长期对象)。

四、总结:回收器选型与调优核心

选择GC回收器的核心是"匹配业务场景",而非追求"最先进",不同回收器的适配场景与调优方向差异显著。

回收器 核心优势 适用场景
Serial GC 简单高效,无线程交互开销 单核CPU、客户端应用
Parallel GC 吞吐量优先,多核加速明显 后台运算、大数据处理
G1 GC 可控停顿,性能均衡 服务端通用场景(4GB+堆内存)
ZGC/Shenandoah 超低延迟,支持TB级大内存 实时服务、云原生大内存场景

调优核心建议:

  • 优先使用默认回收器(JDK 9+用G1),通过GC日志分析瓶颈,避免盲目切换回收器;
  • 低延迟场景优先选ZGC/Shenandoah,吞吐量场景首选Parallel GC,通用服务端场景用G1即可;
  • 控制堆大小(避免过大导致GC时间过长),根据硬件配置调整Region大小、并发线程数等参数,贴合实际运行负载。

GC的本质是"内存管理的权衡艺术"------没有完美的回收器,只有最适合业务的选择。掌握底层原理、各回收器执行逻辑及差异,才能在性能问题出现时快速定位、精准调优,从容应对面试与线上实战挑战。

在Java开发中,"垃圾回收(GC)"是绕不开的核心话题------它既帮我们规避了手动管理内存的繁琐,也可能因不合理的回收策略引发线上性能瓶颈。不少开发者对GC的理解停留在"自动回收垃圾",但背后的可达性分析原理、不同回收器的执行逻辑的差异,才是面试和调优的关键。

本文将从底层原理到实际实现,层层拆解:先讲透可达性分析的核心机制,再逐一剖析各代回收器的执行过程与算法选型,帮你建立完整的GC知识体系。

一、根基:可达性分析的具体原理机制

GC的核心前提是"识别垃圾"------即判断哪些对象不再被使用。早期的"引用计数法"因循环引用问题被淘汰,而HotSpot虚拟机采用的可达性分析算法,是当前最成熟、最通用的垃圾判定方案。

1. 核心思想

以"GC Roots"为起点,构建对象引用关系图,遍历所有可达的对象并标记为"存活",未被标记的对象则判定为"垃圾",等待回收。类比来说,GC Roots相当于"树根",可达对象是"树枝",垃圾则是脱离树干的"枯叶"。

2. GC Roots的具体分类(面试高频)

并非所有对象都能作为GC Roots,必须是"绝对不会被回收"的核心引用,具体包括4类:

  • 虚拟机栈引用对象:栈帧中的局部变量表、操作数栈引用的对象(比如方法内定义的局部变量);
  • 方法区静态属性引用对象 :类的static变量引用的对象(如private static User user = new User());
  • 方法区常量引用对象:字符串常量池、Class常量引用的对象;
  • 本地方法栈引用对象:Native方法(如JNI调用)引用的对象。

3. 具体实现、核心组件与回收器差异

可达性分析的高效执行,依赖HotSpot底层的核心机制支撑,同时不同回收器为适配性能目标,在实现细节、组件选型上存在显著差异。以下从核心实现、卡表机制、回收器差异三方面展开,拆解底层逻辑。

(1)可达性分析的核心实现流程

完整的可达性分析分为"根枚举-对象图遍历-并发修正"三步,每一步均有针对性优化,平衡准确性与效率:

  1. 根枚举:基于OopMap的快速定位
    根枚举的核心是快速找到所有GC Roots,HotSpot通过`OopMap(普通对象指针映射表)`规避全栈遍历的低效问题。在方法JIT编译、类加载阶段,虚拟机自动记录栈帧中局部变量表、操作数栈里引用类型数据的内存偏移量,生成OopMap。初始标记阶段(STW),无需遍历整个虚拟机栈/本地方法栈,仅扫描OopMap记录的位置,即可快速定位根引用对应的对象,此阶段耗时可压缩至毫秒级。此外,方法区的静态属性、常量引用,通过遍历元数据(Class对象的静态变量表、常量池)补充,结合OopMap完成全量GC Roots枚举。
  2. 对象图遍历:分层优化减少扫描范围
    从根引用出发,通过对象头中的引用指针递归遍历可达对象,标记对象头Mark Word中的"存活标记位"。为避免全堆扫描,不同内存管理模式(分代/分区)采用不同优化:分代场景用卡表处理跨代引用,分区场景用记忆集处理跨Region引用,仅针对性扫描关联区域,而非全堆遍历。
  3. 并发修正:解决并发标记漏标问题
    现代回收器(G1/ZGC)为减少STW,采用并发标记模式,需通过特殊机制修正应用线程修改引用导致的漏标问题,不同回收器方案差异显著(后文详述)。

(2)卡表(Card Table)的具体实现

卡表是分代回收场景中处理"老年代→年轻代"跨代引用的核心组件,本质是通过空间换时间,避免跨代遍历全堆,其底层实现细节直接影响回收效率:

  1. 核心结构
    卡表是一块连续的内存区域,按**512字节**粒度将老年代划分为若干"卡页",每个卡页对应卡表中的一个标记位(通常1字节)。标记位状态分为"干净"(无跨代引用)和"肮脏"(存在老年代→年轻代引用),通过位运算快速更新和读取。
  2. 标记触发机制
    当老年代对象引用年轻代对象时,虚拟机通过**写屏障(Write Barrier)**自动标记对应卡页为"肮脏"。写屏障是插入在对象引用赋值操作后的一段轻量代码,无需开发者干预,仅增加微小开销,即可精准追踪跨代引用变更。
  3. 扫描与清理逻辑
    年轻代GC时,无需遍历整个老年代,仅扫描卡表中标记为"肮脏"的卡页,逐个检查卡页内的老年代对象是否引用年轻代存活对象,验证后将卡页标记回"干净",大幅减少扫描范围。

(3)不同回收器的实现差异对比

各回收器因性能目标(吞吐量/低延迟)、内存管理模式(分代/分区)不同,在可达性分析实现、卡表/记忆集使用上差异明显,直接决定其适用场景:

回收器类型 可达性分析核心实现 跨引用处理组件 并发漏标解决方案
Serial GC(串行) 单线程执行,OopMap根枚举+全堆遍历,全程STW,无并发阶段 卡表(分代场景),逻辑简单,仅适配年轻代标记-复制 无并发阶段,无需漏标修正
Parallel GC(并行吞吐量优先) 多线程并行根枚举+遍历,STW时间短于Serial,依赖OopMap优化 卡表(分代场景),多线程并行扫描肮脏卡页,提升效率 无并发阶段,无需漏标修正
CMS GC(并发低延迟) 初始/重新标记(STW+OopMap)+ 并发标记,减少STW占比 卡表(分代场景),写屏障标记肮脏页,重新标记阶段扫描 增量标记+重新标记,修正并发标记漏标对象
G1 GC(分区均衡型) 初始/最终标记(STW+OopMap)+ 并发标记,按Region分区遍历 Remembered Set(记忆集)+ 卡表,每个Region维护跨Region引用 SATB(快照原子化),记录并发引用变更,最终标记阶段批量修正
ZGC(超低延迟大内存) 初始/最终标记(STW+颜色指针)+ 全阶段并发遍历,无全堆扫描 无卡表/记忆集,依赖颜色指针+读屏障追踪跨Region引用 读屏障拦截引用访问,自动标记漏标对象,无需快照

补充说明:Shenandoah GC与ZGC目标一致,均为超低延迟,但采用软件层面的转发指针替代颜色指针,跨引用处理依赖连接矩阵(优化跨Region引用),无需硬件支持,兼容性更强,小堆内存场景性能更优。

可达性分析并非遍历全堆对象,而是分阶段高效执行,同时解决跨代引用、并发漏标等问题:

  1. 初始标记:仅标记GC Roots直接关联的对象,此阶段需STW(Stop-The-World,暂停应用线程),但耗时极短(仅扫描根引用,不遍历对象图);
  2. 并发遍历:从初始标记的对象出发,并发遍历整个对象引用图,标记所有存活对象(无需STW,GC线程与应用线程并行执行);
  3. 问题解决
  • 跨代引用:通过"卡表(Card Table)"记录老年代对年轻代的引用,避免全堆扫描,G1、ParNew等回收器均采用此方案;
  • 并发漏标:因应用线程修改引用,可能导致部分对象漏标,G1用SATB(快照原子化)机制、ZGC用读屏障标记,确保标记准确性。

二、核心:三大基础垃圾回收算法

所有回收器的底层都基于以下三种算法,或其组合优化,不同算法的取舍直接决定回收器的性能特性:

1. 标记-清除算法(Mark-Sweep)

最基础的算法,分为"标记"和"清除"两阶段:

  1. 标记:通过可达性分析标记所有存活对象;
  2. 清除:遍历内存区域,释放未标记的垃圾对象,将空闲内存加入链表。

优缺点:实现简单、内存利用率高,但会产生大量内存碎片(导致大对象无法分配),且清除阶段需遍历全堆,效率不稳定。

适用场景:早期Serial GC老年代、CMS老年代(核心算法)。

2. 标记-复制算法(Mark-Copy)

为解决内存碎片问题设计,核心是"空间换时间":

  1. 分区:将内存划分为两个(或多个)区域,仅使用其中一个(From区);
  2. 标记复制:标记存活对象,将其按顺序复制到空闲区域(To区),避免碎片;
  3. 切换:互换From区和To区角色,清空原From区。

优缺点:无内存碎片、回收效率高,但内存利用率低(需预留空闲区),存活对象多时复制开销大。

适用场景:年轻代(对象存活率低),如Serial、Parallel、ParNew等回收器的年轻代。

3. 标记-整理算法(Mark-Compact)

兼顾内存连续性和利用率,是标记-清除的优化版:

  1. 标记:同可达性分析标记存活对象;
  2. 整理:将所有存活对象向内存一端移动,集中形成连续空间;
  3. 清除:释放存活对象末尾的所有内存。

优缺点:无碎片、内存利用率高,但对象移动需更新引用指针,开销较大。

适用场景:老年代(对象存活率高),如Serial Old、Parallel Old回收器。

三、实战:全类型JVM垃圾回收器解析

JVM垃圾回收器分为"经典分代回收器"(基于分代假说)和"新一代回收器"(面向低延迟、大内存),不同回收器的算法选型、执行流程差异显著,需结合业务场景选择。

(一)经典分代回收器(JDK 8及之前主流)

基于"分代假说"(对象朝生夕死、存活越久越难回收),将堆分为年轻代(Eden+Survivor)和老年代,针对性采用算法。

1. Serial GC(串行回收器)

核心特性:单线程回收,全程STW,最简单的回收器,无线程交互开销。

算法选型

  • 年轻代:标记-复制算法(Eden:Survivor=8:1,仅用10%内存作为空闲区,提升利用率);
  • 老年代:标记-整理算法。

执行过程

  1. 年轻代回收:Eden区满触发,复制存活对象到Survivor区(年龄+1),年龄达标(默认15)则晋升老年代,清空Eden和原Survivor区;
  2. 老年代回收:老年代空间不足触发,标记存活对象并整理到内存一端,释放空闲空间。

适用场景:单核CPU、客户端应用、小型服务(对停顿不敏感),JDK 1.3前默认。

2. Parallel GC(并行回收器,吞吐量优先)

核心特性:多线程并行回收,目标是最大化吞吐量(应用运行时间/总时间),JDK 8默认回收器。

算法选型

  • 年轻代:并行标记-复制(多线程同时复制,加速回收);
  • 老年代:并行标记-整理(多线程并行标记+整理,减少STW时间)。

执行过程:与Serial GC流程一致,仅将单线程操作改为多线程并行,STW时间显著缩短(多核环境下)。

适用场景:后台运算、科学计算、大数据处理(对吞吐量要求高,可容忍百毫秒级停顿)。

3. ParNew GC(并行年轻代回收器)

核心特性:Serial GC的多线程版本,仅负责年轻代回收,必须与CMS老年代回收器搭配使用。

算法选型:年轻代并行标记-复制(与Parallel GC年轻代逻辑一致)。

执行过程:同Parallel GC年轻代回收,仅作为CMS的"年轻代搭档",分担回收压力。

局限性:JDK 9标记为废弃,JDK 14移除,被G1回收器替代。

4. CMS GC(并发标记-清除回收器,低延迟优先)

核心特性:以最短停顿时间为目标,老年代回收大部分工作与应用线程并发执行,是JDK 5-8中低延迟场景的首选。

算法选型

  • 年轻代:搭配ParNew,并行标记-复制;
  • 老年代:并发标记-清除(核心创新,减少STW)。

执行过程(老年代核心流程)

  1. 初始标记(STW):标记GC Roots直接关联的对象,耗时极短(毫秒级);
  2. 并发标记:GC线程与应用线程并行,遍历对象图标记存活对象,无STW;
  3. 重新标记(STW):修正并发标记期间因应用线程修改引用导致的漏标,耗时短于初始标记;
  4. 并发清除:GC线程与应用线程并行,清除未标记垃圾对象,无STW。

优缺点:停顿时间短,但会产生内存碎片、占用CPU资源(并发阶段影响吞吐量),无法处理浮动垃圾(并发清除阶段产生的新垃圾,需下次GC回收)。

局限性:JDK 9废弃,JDK 14移除,被G1/ZGC替代。

(二)新一代回收器(JDK 9+主流,低延迟、大内存)

打破分代回收的严格边界,采用Region分区管理,兼顾吞吐量和低延迟,支持TB级堆内存。

1. G1 GC(Garbage-First,区域优先)

核心特性:JDK 9+默认回收器,面向服务端应用,通过"优先回收垃圾最多的Region"实现可控停顿时间。

核心设计:将堆划分为多个大小相等的Region(1MB~32MB,自动计算),每个Region可动态作为Eden、Survivor、Old区,支持混合回收(年轻代+部分老年代)。

算法选型:整体标记-整理,局部标记-复制(Region间复制存活对象,避免碎片)。

执行过程(混合回收核心流程)

  1. 初始标记(STW):依附于Young GC,标记GC Roots直接关联对象,同时通过卡表定位老年代对年轻代的引用;
  2. 并发标记:遍历对象图标记存活对象,通过写屏障维护Remembered Set(记录跨Region引用);
  3. 最终标记(STW):处理SATB机制记录的漏标对象,修正标记结果;
  4. 筛选回收(部分STW):统计各Region回收收益(释放空间/耗时),优先选择高收益Region,复制存活对象到空闲Region,清空原Region;
  5. 混合回收:分批次回收年轻代+部分老年代Region,通过-XX:MaxGCPauseMillis(默认200ms)控制停顿时间。

适用场景:中型到大型堆内存(4GB~64GB),对停顿时间有要求的服务端应用(如电商、金融)。

2. ZGC(Z Garbage Collector,超低延迟)

核心特性:Oracle推出的新一代回收器,目标是停顿时间控制在10ms以内,支持TB级堆内存,全阶段并发(仅初始标记、最终标记短暂STW)。

核心创新:采用颜色指针(硬件级支持)和读屏障,替代传统写屏障,降低并发开销(读屏障开销仅为写屏障的1/5)。

算法选型:并发标记+并发整理(无碎片)。

执行过程

  1. 初始标记(STW,1-2ms):标记GC Roots直接引用对象,标记为灰色;
  2. 并发标记:GC线程与应用线程并行,按颜色指针流转标记(灰→黑,白→灰),读屏障自动标记被引用的白色对象;
  3. 并发整理:选择垃圾占比超25%的Region,复制存活对象到新Region,通过颜色指针重定向引用;
  4. 最终标记(STW,1ms):确认引用变更,重置颜色指针;
  5. 并发清理:释放无存活对象的Region,更新元数据。

适用场景:大内存(16GB+)、超低延迟场景(如实时数据处理、云原生服务)。

3. Shenandoah GC(红帽版低延迟回收器)

核心特性:与ZGC目标一致,但采用软件层面的转发指针(无需硬件支持),兼容性更强,支持分代优化。

核心创新:转发指针(对象头存储新地址)、连接矩阵(优化跨Region引用)。

算法选型:并发标记+并发整理。

与ZGC差异:弱化硬件依赖,小堆(4-16GB)性能更优,分代管理更完善,适合混合工作负载。

四、总结:回收器选型与调优核心

选择GC回收器的核心是"匹配业务场景",而非追求"最先进":

回收器 核心优势 适用场景
Serial GC 简单高效,无线程开销 单核、客户端应用
Parallel GC 吞吐量优先,多核加速 后台运算、大数据处理
G1 GC 可控停顿,均衡性能 服务端通用场景(4GB+堆)
ZGC/Shenandoah 超低延迟,大内存支持 实时服务、云原生大内存场景

调优核心建议:

  • 优先用默认回收器(JDK 9+用G1),通过GC日志分析瓶颈;
  • 低延迟场景优先ZGC/Shenandoah,吞吐量场景用Parallel GC;
  • 控制堆大小(避免过大导致GC时间长),调整Region大小、并发线程数等参数匹配硬件。

GC的本质是"内存管理的权衡艺术"------没有完美的回收器,只有最适合业务的选择。掌握底层原理和各回收器的执行逻辑,才能在性能问题出现时快速定位、精准调优。

相关推荐
缺点内向2 小时前
在 C# 中为 Word 段落添加制表位:使用 Spire.Doc for .NET 实现高效排版
开发语言·c#·自动化·word·.net
yufuu982 小时前
Python在金融科技(FinTech)中的应用
jvm·数据库·python
中科院提名者2 小时前
如何配置go环境并用vscode运行
开发语言·后端·golang
电饭叔2 小时前
GUI by Python 6 一段 gui 代码分析
开发语言·python
OnYoung2 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
2301_822377652 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
qq_12498707532 小时前
基于springboot+vue的家乡特色旅游宣传推荐系统(源码+论文+部署+安装)
java·前端·vue.js·spring boot·毕业设计·计算机毕设·计算机毕业设计
菜菜小狗的学习笔记2 小时前
黑马程序员java web学习笔记--后端进阶(一)AOP
java·笔记·学习
霑潇雨2 小时前
Flink转换算子——filter
java·大数据·flink