JVM垃圾回收(GC,Garbage Collection)是Java自动内存管理的核心机制,它负责识别并回收堆内存中不再被使用的对象,释放内存资源,避免内存泄漏和内存溢出(OOM)。对于Java后端开发者而言,理解GC原理不仅是面试高频考点,更是排查线上性能问题、优化系统吞吐量的关键。
本文将从"垃圾判定→回收算法→垃圾收集器→实战调优"四个维度,系统拆解JVM垃圾回收的核心知识。
一、GC的核心作用与回收范围
1.1 GC的核心目标
Java开发者无需手动调用free()释放内存,GC的核心目标是:
-
识别堆内存中"死亡"的对象(不再被引用的对象);
-
安全回收死亡对象占用的内存,重新分配给新对象;
-
尽可能降低回收过程对应用程序的影响(减少STW时间)。
1.2 GC的回收范围
JVM运行时数据区中,GC主要作用于堆内存(所有对象实例的分配区域),其次是方法区(元空间,仅回收无用类),其他区域(虚拟机栈、本地方法栈、程序计数器)为线程私有,随线程销毁自动释放,不参与GC。
堆内存进一步分为新生代和老年代,不同区域的对象生命周期不同,GC策略也不同:
-
新生代:对象"朝生夕死",存活率极低(通常不足10%),GC频率高、耗时短;
-
老年代:对象存活时间长(多次GC后仍存活),存活率高,GC频率低、耗时长。
关键提醒:堆内存是GC的主战场,理解新生代与老年代的划分,是掌握GC算法和收集器的前提。
二、垃圾判定:如何判断对象"已死亡"?
GC的第一步是"识别垃圾"------即判断哪些对象不再被程序使用,目前JVM主流采用可达性分析算法,淘汰了存在致命缺陷的引用计数法。
2.1 被淘汰的方案:引用计数法
原理:为每个对象维护一个引用计数器,当对象被引用时计数器+1,引用失效时计数器-1;当计数器为0时,判定为垃圾。
优点:实现简单、实时性高,无需暂停用户线程。
致命缺陷:无法解决循环引用问题。例如,对象A引用对象B,对象B引用对象A,两者计数器均为1,但外部无任何引用,理论上应被回收,但引用计数法无法识别,会导致内存泄漏。因此,JVM未采用此方案(Python等语言使用该方案,但需额外处理循环引用)。
2.2 JVM主流方案:可达性分析算法
原理:以"GC Roots"为起点,遍历对象的引用链,若一个对象无法通过任何引用链到达GC Roots,则判定为垃圾(不可达对象)。
核心:GC Roots的4类核心来源
-
虚拟机栈(栈帧局部变量表)中引用的对象(如方法内的局部变量、参数);
-
方法区中类静态属性引用的对象(如
static Object obj = new Object()); -
方法区中常量引用的对象;
-
本地方法栈中JNI(本地方法)引用的对象。
对象死亡的"两次标记"机制
不可达对象并非立即被回收,需经历两次标记,确保回收的准确性:
-
第一次标记:可达性分析发现对象不可达,标记为"待回收";
-
筛选判断:检查该对象是否重写了
finalize()方法,且该方法未被执行过;-
若未重写或已执行,则直接判定为"死亡",等待回收;
-
若重写且未执行,则将对象放入F-Queue队列,由Finalizer线程执行
finalize()方法;
-
-
第二次标记:执行
finalize()后,若对象重新与GC Roots建立引用链("复活"),则移除"待回收"标记;否则,最终判定为"死亡",等待回收。
注意:finalize()方法优先级极低,不保证一定执行,且执行时间不可控,开发中严禁依赖该方法进行资源释放(建议用try-finally替代)。
三、核心垃圾回收算法:GC的"底层实现逻辑"
识别垃圾后,GC需要通过特定算法回收内存,不同算法适用于不同场景(新生代/老年代)。JVM的垃圾回收算法核心有4种,其中分代收集算法是目前主流的组合策略。
3.1 标记-清除算法(Mark-Sweep)
核心原理(两步执行)
-
标记阶段:从GC Roots出发,标记所有可达对象;
-
清除阶段:遍历整个堆内存,回收所有未标记的垃圾对象,释放内存空间。
优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,无需移动对象,执行效率高 | 1. 内存碎片化严重(回收后产生大量不连续内存块,大对象无法分配时触发Full GC);2. 标记和清除均需遍历全堆,STW时间长 |
适用场景
仅适用于老年代(对象存活率高,移动成本高),极少单独使用,早期CMS收集器的老年代回收基于此算法优化。
3.2 标记-复制算法(Mark-Copy)
核心原理(空间换时间)
将堆内存划分为两个大小相等的区域(From区和To区),仅使用From区分配对象;GC时执行以下步骤:
-
标记阶段:标记From区中所有可达对象;
-
复制阶段:将所有标记的存活对象,复制到To区(按内存地址连续排列);
-
交换阶段:清空From区,交换From区和To区的角色,下次GC使用新的From区。
优化版:Appel式分配(JVM新生代实际采用)
为解决"内存利用率低"的问题,JVM将新生代划分为1个Eden区 + 2个Survivor区(默认比例8:1:1),而非等大分区:
-
大部分对象在Eden区创建,GC时标记Eden区+From Survivor区的存活对象,复制到To Survivor区;
-
若To Survivor区空间不足,存活对象直接晋升到老年代;
-
交换From和To Survivor区的角色,重复上述过程。
优缺点
| 优点 | 缺点 |
|---|---|
| 1. 无内存碎片(复制后对象连续排列);2. 回收效率高(仅处理存活对象,新生代存活率低);3. 内存分配时仅需移动指针,效率极高 | 1. 内存利用率低(需预留To区,优化后仍损失10%新生代空间);2. 老年代对象存活率高,复制成本极高,不适用 |
适用场景
新生代核心回收算法(所有收集器的新生代回收均基于此),契合新生代对象"朝生夕死"的特点。
3.3 标记-整理算法(Mark-Compact)
核心原理(标记+移动+清除)
-
标记阶段:同标记-清除算法,标记所有可达对象;
-
整理阶段:将所有标记的存活对象,向堆内存一端压缩移动,保证存活对象连续排列;
-
清除阶段:清理内存边界外的所有垃圾对象,释放连续的内存空间。
优缺点
| 优点 | 缺点 |
|---|---|
| 1. 无内存碎片;2. 内存利用率100%(无需预留空间);3. 适合存活率高的对象 | 1. 移动对象成本高(需更新所有对象的引用地址);2. 整理过程需暂停所有用户线程,STW时间长 |
适用场景
老年代核心算法(如Serial Old、Parallel Old收集器),平衡了内存碎片化和内存利用率。
3.4 分代收集算法(Generational Collection)
并非独立算法,而是基于对象生命周期的"组合策略",结合上述三种算法的优势,是目前所有JVM收集器的核心实现逻辑:
-
新生代:对象存活率低 → 采用标记-复制算法(高效、无碎片);
-
老年代:对象存活率高、体积大 → 采用标记-清除/标记-整理算法(避免高复制成本);
-
元空间(方法区):极少回收,仅在无用类卸载时触发(如类加载器被回收、无对象引用该类)。
核心优势:针对不同区域选择最优算法,降低整体GC开销------新生代Minor GC频率高但耗时短,老年代Major GC/Full GC频率低但耗时长,平衡了回收效率和应用性能。
四、主流垃圾收集器:GC的"具体实现"
垃圾回收算法是"底层逻辑",垃圾收集器是"具体实现"。JVM提供了多种收集器,适配不同的应用场景(低延迟、高吞吐量),核心分为三大类:串行收集器、并行收集器、并发收集器。
以下重点讲解目前生产环境中常用的4种收集器,以及已被废弃的CMS收集器(面试高频)。
4.1 串行收集器(Serial GC)
最基础、最简单的收集器,采用"单线程"执行GC,回收期间暂停所有用户线程(STW),新生代采用标记-复制算法,老年代采用标记-整理算法。
特点:实现简单、内存开销小,STW时间长,仅适用于单CPU、小堆内存(<4GB)场景(如桌面应用、嵌入式系统),生产环境(服务器)极少使用。
启用参数:-XX:+UseSerialGC
4.2 并行收集器(Parallel GC)
JDK8默认收集器,又称"吞吐量优先收集器",采用"多线程"执行GC,新生代采用标记-复制算法,老年代采用标记-整理算法。
核心目标:最大化系统吞吐量(吞吐量=应用运行时间/(应用运行时间+GC时间)),通过多线程并行回收,减少GC总耗时。
特点:吞吐量高,STW时间比Serial GC短,适用于高吞吐量需求的场景(如后台任务、数据计算),但STW时间仍不可控,不适合低延迟场景。
启用参数:-XX:+UseParallelGC(新生代并行)、-XX:+UseParallelOldGC(老年代并行)
4.3 并发标记清除收集器(CMS)
一款"低延迟优先"的收集器,核心目标是减少STW时间(控制在100ms内),仅作用于老年代,新生代依赖ParNew收集器(并行标记-复制)。
核心流程(4个阶段)
-
初始标记:暂停所有用户线程(STW),标记GC Roots直接引用的对象,耗时极短(毫秒级);
-
并发标记:恢复用户线程,GC线程与应用线程并发执行,遍历GC Roots引用链,标记所有可达对象;
-
重新标记:暂停所有用户线程(STW),修正并发标记期间因用户线程操作导致的标记偏差,耗时短(十毫秒级);
-
并发清除:恢复用户线程,GC线程与应用线程并发执行,回收未标记的垃圾对象。
优缺点
| 优点 | 缺点 |
|---|---|
| STW时间短,适合低延迟场景(如电商支付、实时接口);对小堆(2-10GB)友好 | 1. 内存碎片严重(并发清除不移动对象);2. CPU开销高(并发阶段GC线程与应用线程抢CPU);3. 存在浮动垃圾(并发清除阶段产生的新垃圾,需预留内存);4. 已被废弃(Java 14后移除) |
适用场景
仅用于JDK8及以下的旧系统维护(堆2-10GB),新系统不推荐使用,建议迁移至G1收集器。
启用参数:-XX:+UseConcMarkSweepGC
4.4 G1收集器(Garbage-First)
JDK9及以上默认收集器,一款"平衡延迟与吞吐量"的收集器,融合了分代收集和区域化管理的思想,适用于中大型堆内存(4-64GB)。
核心特点
-
Region化管理:将堆内存划分为多个大小相等的Region(1MB-32MB),每个Region可动态切换为新生代、老年代,无需手动划分区域大小;
-
混合回收:优先回收垃圾最多的Region(Garbage-First),兼顾新生代和老年代回收;
-
可控停顿:通过停顿预测模型,控制STW时间在目标范围内(默认200ms),平衡延迟与吞吐量。
优缺点
| 优点 | 缺点 |
|---|---|
| 1. 延迟与吞吐量平衡,适配多数企业级应用(Web服务、电商系统);2. 低内存碎片(筛选回收阶段复制对象);3. 动态适配对象分配特性,无需手动调优;4. 成熟稳定 | 1. 内存开销较高(记忆集占堆5%-10%);2. 小堆(<4GB)下性价比低;3. 大堆(>64GB)时延迟可能上升 |
适用场景
中大型堆内存(4-64GB)、需要平衡延迟与吞吐量的场景(如Spring Boot应用、微服务、电商系统),是目前生产环境的首选收集器。
启用参数:-XX:+UseG1GC
4.5 新一代收集器:ZGC
JDK11引入、JDK17稳定的新一代收集器,核心目标是"超低延迟+超大堆",支持最大16TB堆内存,STW时间控制在1ms内,适用于极致低延迟场景(如高频交易、实时数据分析)。
核心优势:全阶段并发(仅初始/最终标记有微秒级STW)、几乎无内存碎片、低CPU开销,但小堆场景性价比低,工具链适配滞后,目前尚未大规模普及。
启用参数:-XX:+UseZGC
4.6 收集器选型建议
-
小堆(<4GB)+ 吞吐量优先:Serial GC(简单场景)、Parallel GC(性能更优);
-
中大型堆(4-64GB)+ 平衡需求:G1 GC(首选,默认);
-
超大堆(≥32GB)+ 超低延迟:ZGC(JDK17+);
-
旧系统(JDK8)+ 低延迟:暂时保留CMS,建议逐步迁移至G1。
五、GC关键概念与实战调优基础
5.1 核心GC术语
-
STW(Stop The World):GC回收期间,暂停所有用户线程,避免引用链动态变化,是影响应用性能的关键(尽可能缩短STW时间);
-
Minor GC:仅回收新生代的GC,触发条件为Eden区满,频率高、耗时短;
-
Major GC:仅回收老年代的GC,触发条件为老年代空间不足、晋升对象超过老年代剩余空间,频率低、耗时长;
-
Full GC :回收新生代+老年代+元空间的GC,触发条件为Major GC失败、元空间满、显式调用
System.gc(),STW时间最长,应尽量避免; -
空间分配担保:Minor GC前,JVM检查老年代最大可用连续空间是否≥新生代所有对象总大小,若不足则触发Full GC。
5.2 实战调优核心原则
-
优先优化堆内存大小:根据应用场景设置合理的堆内存(-Xms=初始堆大小,-Xmx=最大堆大小,建议两者设置一致,避免频繁扩容);
-
选择合适的收集器:结合堆大小、延迟需求选型,避免盲目追新(如小堆用G1反而性能下降);
-
减少对象创建频率:避免频繁创建短期对象(如循环内创建字符串),减少Minor GC频率;
-
避免内存泄漏:及时释放无用引用(如静态集合持有对象、IO流未关闭),防止老年代溢出;
-
监控GC状态:通过JDK自带工具(jstat、jmap、jconsole)监控GC频率、STW时间,针对性调优。
5.3 常用GC监控工具
-
jstat:实时监控GC统计信息(如GC次数、耗时、堆内存使用情况); -
jmap:生成堆内存快照,分析对象分布、排查内存泄漏; -
jconsole:图形化工具,直观查看GC状态、堆内存变化、线程状态; -
VisualVM:功能强大的可视化工具,支持GC分析、内存快照分析、性能采样。
六、面试高频考点总结
-
GC的核心作用?回收堆内存中死亡的对象,避免内存泄漏和OOM,降低对应用的影响。
-
垃圾判定的两种方式?引用计数法(缺陷:循环引用)、可达性分析算法(JVM主流)。
-
GC Roots包含哪些?虚拟机栈局部变量、方法区静态变量/常量、本地方法栈JNI引用。
-
三大基础GC算法的优缺点及适用场景?标记-清除(碎片化)、标记-复制(新生代)、标记-整理(老年代)。
-
分代收集算法的核心思想?按对象生命周期分代,新生代用标记-复制,老年代用标记-清除/整理。
-
CMS和G1的区别?CMS低延迟但有碎片、已废弃;G1平衡延迟与吞吐量,无碎片、目前主流。
-
如何减少STW时间?选择合适的收集器、优化堆内存大小、减少对象创建、避免Full GC。
七、总结
JVM垃圾回收的核心是"识别垃圾→回收垃圾→优化回收效率",其演进本质是"延迟、吞吐量、内存开销"的平衡艺术:从Serial GC的简单单线程,到Parallel GC的高吞吐量,再到CMS的低延迟,最后到G1、ZGC的平衡与突破,每一款收集器都对应特定的应用场景。
对于开发者而言,无需深入底层源码实现,重点掌握"垃圾判定原理、核心算法、收集器选型、调优原则",既能应对面试,也能在生产环境中排查GC相关的性能问题,避免内存溢出和系统卡顿,提升应用的稳定性和性能。