JVM GC 机制详解:算法、分代、回收器、GC 类型一篇讲透

前言

JVM 的垃圾回收是 Java 面试里的高频重点,也是线上问题排查时绕不开的一块。

很多人学 GC 的时候容易陷入两个误区:

  • 只背概念,不知道这些机制为什么存在

  • 只记回收器名字,不知道它们到底解决了什么问题

这篇笔记的目标不是把 GC 讲得特别"学术",而是帮你建立一套完整的理解框架:

先知道 JVM 为什么需要 GC,再理解对象怎么判断存活,然后把回收算法、分代思想、回收器和 GC 类型串起来。


一、为什么 JVM 需要垃圾回收

Java 和 C、C++ 最大的一个区别,就是 Java 不需要程序员手动释放内存。

对象创建后,JVM 会负责管理它的生命周期。当一个对象已经不再被程序使用时,就应该把它占用的内存回收掉,否则对象越积越多,最终就会导致内存溢出。

所以 GC 的本质就是一句话:

自动找出"已经没用的对象",并把它们占用的内存清理掉。

这样做的好处是:

  • 降低手动管理内存的复杂度

  • 减少野指针、重复释放等问题

  • 让开发者更专注于业务逻辑

但代价也很明显:

  • GC 会占用 CPU 资源

  • GC 期间可能发生停顿

  • 如果内存分配和回收策略不合理,性能会明显抖动


二、先补一个背景:JVM 运行时内存区域

在讲 GC 前,先明确一件事:

不是 JVM 所有内存区域都会发生垃圾回收,GC 重点关注的是堆和方法区。

JVM 运行时内存大致可以分成下面几块:

1. 程序计数器

线程私有,记录当前线程执行到哪条字节码指令。这个区域很小,也基本不涉及 GC。

2. 虚拟机栈

线程私有,存放方法调用过程中的栈帧、局部变量表等。方法执行结束,对应栈帧出栈,内存自然释放,也不属于典型 GC 处理范围。

3. 本地方法栈

和虚拟机栈类似,只不过服务的是 Native 方法。

4. 堆

堆是 Java 对象实例最主要的存储区域,也是垃圾回收最核心的区域。

5. 方法区

方法区主要存储类元信息、常量、静态变量、JIT 编译后的代码缓存等。HotSpot 里早期常提永久代,后来更多提元空间。

一句话总结:

GC 主要回收的是堆中的对象,以及方法区中不再使用的类和常量。


三、如何判断对象已经"死了"

垃圾回收的第一步,不是回收,而是判断哪些对象还能用,哪些对象已经没用了。

1. 引用计数法

引用计数法的思路很直观:

  • 给对象加一个引用计数器

  • 每被一个地方引用,计数器加一

  • 每失去一个引用,计数器减一

  • 当计数器变成 0,就说明对象可以回收

这个方法看起来很简单,但有一个致命问题:

它无法很好地解决对象循环引用的问题。

比如 A 引用 B,B 也引用 A,虽然这两个对象其实都已经不再被外部使用,但它们的计数器都不为 0,就无法回收。

2. 可达性分析

JVM 主流实现采用的是可达性分析算法

它的核心思想是:

从一组叫作 GC Roots 的对象作为起点往下搜索,能被这些起点直接或间接关联到的对象就是"存活对象",反之就是可回收对象。

常见的 GC Roots 包括:

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈 JNI 引用的对象

  • 被同步锁持有的对象

所以判断对象是否存活,不是看"有没有引用",而是看:

它是否还能从 GC Roots 追踪到。


四、Java 中几种常见引用类型

Java 里不只有"有引用"和"没引用"两种状态,还进一步细分了引用强度。

1. 强引用

最普通的引用就是强引用,比如:

复制代码
Object obj = new Object();

只要强引用还在,GC 一般就不会回收这个对象。

2. 软引用

软引用用于描述那些"有用但不是必须"的对象。内存不足时,JVM 会优先回收软引用对象。

典型场景是缓存。

3. 弱引用

弱引用比软引用更弱。只要发生 GC,不管当前内存紧不紧张,弱引用关联的对象都可能被回收。

典型场景是 ThreadLocalMap 中的 Key。

4. 虚引用

虚引用几乎不影响对象生命周期,主要用来在对象被回收时收到通知,常用于更底层的资源管理。

一句话理解:

强引用最稳,软引用适合缓存,弱引用更容易被回收,虚引用主要是配合回收通知。


五、垃圾回收的基础算法有哪些

这部分是 GC 的底层核心。后面的分代回收器,本质上都是在这些基础算法上做组合和优化。

1. 标记-清除算法

流程分两步:

  1. 先标记出所有需要回收的对象

  2. 再统一清除这些对象

优点是实现简单。

缺点主要有两个:

  • 效率一般

  • 会产生内存碎片

内存碎片的意思是:虽然总空闲内存够,但它们不连续,大对象可能没法直接分配。

2. 复制算法

复制算法的思路是:

  • 把内存分成两块

  • 每次只使用其中一块

  • 发生 GC 时,把存活对象复制到另一块

  • 然后一次性清空原来的那一整块

优点:

  • 实现简单

  • 没有内存碎片

  • 适合"存活对象少、死亡对象多"的场景

缺点:

  • 需要额外预留一部分空间

  • 如果存活对象很多,复制成本高

3. 标记-整理算法

标记-整理可以看成标记-清除的改良版。

流程是:

  1. 标记存活对象

  2. 让所有存活对象向一端移动

  3. 清理边界之外的内存

优点是:

  • 没有内存碎片

  • 适合老年代这种对象存活率较高的场景

缺点是:

  • 整理过程有移动成本

4. 分代收集算法

严格来说,分代收集不是一个基础算法,而是一种设计思想。

它的核心判断是:

不同生命周期的对象,适合不同的回收策略。

因为大多数 Java 对象都"朝生夕死",少数对象会长期存活,所以 JVM 把堆划分为不同区域,针对不同区域采用不同算法,这就是分代回收思想。


六、为什么要分代回收

分代回收的本质依据是对象生命周期特征不同。

大多数对象都有两个典型特点:

  • 刚创建出来很快就没用了

  • 少量对象会一直活很久

所以 JVM 通常会把堆分成:

  • 新生代

  • 老年代

有些资料还会额外提永久代或元空间,但那更多属于方法区的实现,不是传统意义上的"堆分代"。

1. 新生代

新生代主要放新创建的对象。这里对象数量多,但大部分死得快,所以适合使用复制算法

2. 老年代

老年代主要放存活时间较长的对象。这里对象存活率高,不适合简单复制,所以更常采用标记-清除标记-整理思想。

一句话总结:

新生代追求"快速回收大量短命对象",老年代关注"稳定回收长期存活对象"。


七、新生代为什么分 Eden、Survivor

新生代一般不是一整块连续区域,而是进一步分成:

  • Eden 区

  • Survivor From 区

  • Survivor To 区

默认情况下,大部分对象会先在 Eden 里分配。

当 Eden 空间不够时,会触发一次 Minor GC。GC 后还活着的对象会被复制到 Survivor 区。之后每经历一次回收还活着,对象年龄就会加一。

当对象年龄达到某个阈值后,就会晋升到老年代。

这里常说的新生代比例,一般是:

Eden : Survivor From : Survivor To = 8 : 1 : 1

这样设计的原因是:

  • 大多数对象都死得很快,不需要给 Survivor 留太大空间

  • 保留两个 Survivor 区,方便复制和年龄统计


八、对象什么时候进入老年代

对象进入老年代,常见有几种情况:

1. 年龄达到阈值

对象每在 Survivor 区熬过一次 GC,年龄就会加一。达到晋升阈值后进入老年代。

2. 大对象直接进入老年代

如果对象特别大,比如超大的数组,为了避免在 Eden 和 Survivor 之间来回复制,JVM 可能会让它直接进入老年代。

3. 动态年龄判断

如果 Survivor 中相同年龄对象的总大小超过了某个比例,不一定非得等到设定阈值,也可能提前晋升老年代。


九、GC 的几种常见类型

这部分面试里很高频,最好能准确区分。

1. Minor GC

也叫 Young GC,发生在新生代。

特点:

  • 频率高

  • 回收速度快

  • 停顿时间通常较短

因为新生代大部分对象都会在一次次 Minor GC 中被直接清掉。

2. Major GC

Major GC 一般指针对老年代的 GC。

但要注意:

在不同资料和不同实现里,Major GC 的叫法并不总是完全统一。

所以面试里更稳妥的说法是:

  • Minor GC:新生代回收

  • Full GC:整个堆甚至连方法区一起回收

3. Full GC

Full GC 往往会回收:

  • 新生代

  • 老年代

  • 方法区

Full GC 的特点通常是:

  • 停顿时间长

  • 对系统影响大

  • 应尽量避免频繁发生

4. Mixed GC

Mixed GC 是 G1 里比较典型的概念。

它不是只回收新生代,也不是整个堆全回收,而是:

在回收新生代的同时,选择一部分收益高的老年代 Region 一起回收。

这样既能控制停顿,又能逐步回收老年代。


十、经典垃圾回收器怎么理解

回收算法解决的是"怎么回收",回收器解决的是"谁来执行这些算法,以及怎么权衡吞吐量、停顿时间和资源消耗"。


十一、Serial 回收器

Serial 是最基础、最简单的收集器。

特点:

  • 单线程回收

  • 发生 GC 时必须 Stop-The-World

  • 实现简单,额外开销小

它适合:

  • 单核环境

  • Client 模式

  • 内存不大、对停顿不敏感的场景

它的缺点也很明显:

  • 一旦堆大、并发高,停顿会比较明显

十二、ParNew 回收器

ParNew 可以理解成 Serial 的多线程版本,主要工作在新生代。

特点:

  • 多线程并行回收

  • 仍然会 STW

  • 在多核机器上比 Serial 更高效

它过去经常和 CMS 搭配使用。


十三、Parallel Scavenge 回收器

Parallel Scavenge 也是新生代回收器,重点不是尽量缩短单次停顿,而是追求高吞吐量。

这里的吞吐量可以简单理解为:

用户代码执行时间 /(用户代码执行时间 + 垃圾回收时间)

所以它适合:

  • 后台计算

  • 批处理任务

  • 更看重整体吞吐而不是单次响应时间的场景

它常和 Parallel Old 搭配。


十四、Serial Old 和 Parallel Old

这两个都是老年代回收器。

1. Serial Old

Serial Old 是 Serial 的老年代版本,单线程,通常采用标记-整理算法。

2. Parallel Old

Parallel Old 是 Parallel Scavenge 的老年代搭档,多线程,关注吞吐量。

如果应用更关注整体处理能力而不是响应延迟,这套组合会比较常见。


十五、CMS 回收器

CMS,全称 Concurrent Mark Sweep,是一款以低停顿为目标的老年代回收器。

它的核心思想是:

尽量让垃圾回收和用户线程并发执行,减少停顿时间。

CMS 的主要过程一般包括:

  1. 初始标记

  2. 并发标记

  3. 重新标记

  4. 并发清除

其中:

  • 初始标记和重新标记需要 STW

  • 并发标记和并发清除可以和用户线程同时进行

CMS 的优点是:

  • 停顿时间短

  • 对响应时间敏感的系统比较友好

CMS 的缺点也很典型:

  • 会产生内存碎片

  • 对 CPU 资源敏感

  • 并发阶段如果回收跟不上分配速度,可能触发 Concurrent Mode Failure

  • 后期已经被更新的回收器逐步替代

一句话总结:

CMS 的核心价值是低停顿,但它用空间碎片和更复杂的运行成本换来了这个优势。


十六、G1 回收器

G1,Garbage First,是现在非常主流的一款服务端垃圾回收器。

它和传统分代回收器一个很大的区别在于:

G1 不再强调固定连续的新生代、老年代物理边界,而是把整个堆划分成多个大小相等的 Region。

这些 Region 在逻辑上仍然可以扮演 Eden、Survivor、Old 等角色。

G1 的核心特点

1. Region 化

把堆拆成很多小块,而不是简单地分成一整块新生代和一整块老年代。

2. 可预测停顿

G1 会根据回收收益优先选择垃圾最多、回收价值最高的 Region,这也是 "Garbage First" 名字的来源。

3. Mixed GC

G1 不需要每次都 Full GC 才处理老年代,而是可以在回收新生代时顺带挑一些老年代 Region 一起回收。

G1 的适用场景

  • 大堆内存

  • 多核服务器

  • 既希望吞吐不错,又希望停顿尽量可控

G1 的不足

  • 实现复杂

  • 在某些极端场景下未必总比 CMS 或 Parallel 更有优势

一句话总结:

G1 的核心不是"绝对最快",而是"在大堆场景下尽量把停顿控制得更可预测"。


十七、ZGC 和 Shenandoah 简单了解

如果面试官问到新一代低延迟 GC,可以简单提一下 ZGC 和 Shenandoah。

1. ZGC

ZGC 的目标是把停顿时间压得非常低,哪怕堆很大也尽量保持低停顿。

特点:

  • 面向超大堆

  • 停顿时间极短

  • 更多通过并发阶段完成大部分工作

2. Shenandoah

Shenandoah 也是低停顿回收器,设计目标和 ZGC 类似,也强调尽量并发执行回收过程。

如果只是常规面试,知道到这里通常就够了。除非岗位明确对 JVM 调优要求很高,否则一般不会深挖底层实现细节。


十八、什么是 Stop-The-World

Stop-The-World,简称 STW,指的是:

在 GC 的某些关键阶段,JVM 必须暂停所有用户线程,只让垃圾回收线程工作。

这也是为什么 GC 会影响响应时间的根本原因。

需要注意的是:

  • 不是只有 Full GC 才会 STW

  • Minor GC 也可能 STW

  • 并发回收器也不是完全不停顿,只是尽量缩短停顿时间

所以判断一个回收器好不好,很多时候看的就是:

  • 停顿多久

  • 停顿是否可控

  • 吞吐量损失大不大


十九、对象一定会被立刻回收吗

不会。

一个对象即使已经不可达,也不代表它会在下一秒立刻被清理。

GC 是按一定时机触发的,具体什么时候回收,要看:

  • 当前内存分配情况

  • GC 触发条件

  • 使用的回收器

  • JVM 参数配置

所以更准确的理解应该是:

"可回收"不等于"立刻回收",而是说明这个对象已经具备了被 GC 清理的资格。


二十、什么情况下容易频繁 Full GC

这类题既能用于面试,也能用于排查线上问题。

常见原因包括:

1. 老年代空间不足

对象晋升太快,或者老年代回收速度跟不上分配速度,都会导致 Full GC。

2. 大对象分配过多

大对象直接进入老年代,容易快速把老年代打满。

3. 内存泄漏

对象其实已经不该用了,但仍然被引用着,导致 GC 无法回收。

4. 元空间不足

如果频繁动态生成类,比如大量反射、代理、热部署不当,也可能导致方法区相关 Full GC。

5. 参数配置不合理

比如新生代太小、晋升阈值不合适、堆大小配置不合理,也会让 Full GC 更频繁。


二十一、如何理解内存泄漏和内存溢出

这两个概念很容易被混用。

1. 内存泄漏

内存泄漏指的是:

对象本来已经没用了,但仍然被某些引用持有,导致 GC 无法回收。

2. 内存溢出

内存溢出指的是:

JVM 已经没有足够内存完成新的分配请求,最终抛出 OOM。

关系是:

  • 内存泄漏不一定立刻 OOM

  • 但严重的内存泄漏经常最终会导致内存溢出


二十二、面试里怎么讲"为什么会有分代"

这是一个很值得背的表达。

可以直接这样说:

JVM 之所以采用分代回收,核心原因是不同对象的生命周期差异很大。大多数对象朝生夕死,适合放在新生代,用复制算法快速清理;少数对象长期存活,适合放在老年代,用标记-清除或标记-整理进行回收。这样做能显著提高 GC 效率,避免所有对象都用同一种回收策略。


二十三、面试里怎么讲"G1 和 CMS 的区别"

可以按下面这个思路回答:

CMS 更强调低停顿,主要针对老年代,核心问题是会产生内存碎片,而且在高并发下可能出现并发失败;G1 则把堆拆成多个 Region,能够在回收新生代的同时回收部分老年代,并且更强调停顿时间的可预测性。简单来说,CMS 是传统低停顿方案,G1 是更现代、更通用的大堆低停顿方案。


二十四、面试里怎么讲"GC 算法和回收器的关系"

这也是很容易被问到的一个点。

可以这样理解:

  • GC 算法回答的是"垃圾怎么回收"

  • 垃圾回收器回答的是"具体由谁、以什么方式执行这些算法"

比如:

  • Serial、ParNew、G1、CMS 是回收器

  • 标记-清除、复制、标记-整理是算法

一句话总结:

算法是理论基础,回收器是工程实现。


二十五、最后给一版适合背诵的总结

如果面试官问"你怎么理解 JVM 的 GC 机制",可以直接按下面这段说:

JVM 的 GC 机制本质上就是自动识别无用对象并回收内存。判断对象是否存活,主流是通过可达性分析,从 GC Roots 出发去查找可达对象。真正执行回收时,底层会用到标记-清除、复制、标记-整理这些算法。由于大多数对象生命周期都很短,所以 JVM 引入了分代回收思想,把堆分成新生代和老年代:新生代对象死亡率高,通常用复制算法;老年代对象存活率高,更适合标记-清除或标记-整理。常见 GC 类型包括 Minor GC、Major GC、Full GC,G1 里还有 Mixed GC。回收器层面,早期有 Serial、ParNew、Parallel、CMS,现在更主流的是 G1,更低停顿场景下还有 ZGC、Shenandoah。理解 GC 的关键,不是死记名字,而是知道每种机制背后在解决什么问题,比如吞吐量、停顿时间、碎片整理和大堆场景下的可控性。

相关推荐
霸道流氓气质1 小时前
ApiPost 中配置自动获取 Token 并调用业务接口完整指南
java·服务器·数据库
独隅1 小时前
IntelliJ IDEA 接入多种AI大模型插件终极指南(2026.1 企业合规版)
java·人工智能·intellij-idea
还是奇怪2 小时前
Simon Willison 用 DSPy 优化 Datasette Agent 提示词:提示工程正在变成可测试的软件工程
java·开发语言·软件工程
zfoo-framework2 小时前
1.ansible安装 2.虚拟机克隆
java
码上解惑2 小时前
从 Dify 工作流说起:常用节点怎么选、怎样组合?
java·人工智能·ai·agent·dify·智能体·spring ai
青山木3 小时前
Hot 100 --- 岛屿数量
java·数据结构·算法·leetcode·深度优先·广度优先
程序员-珍4 小时前
报错下载android sdk失败
android·java
糖果店的幽灵4 小时前
langgraph分支之 - 动态分支(Dynamic Branch)
java·前端·javascript·人工智能·langgraph
吃饱了得干活4 小时前
亿级订单表分库分表设计,从0到1全流程
java·数据库·面试
唐青枫4 小时前
Java Spring Security 实战详解:从登录认证到 JWT 权限控制
java