文章目录
- JVM垃圾回收算法 系统性知识体系总结
- 一、垃圾回收基础概念
- 1.1 垃圾回收的本质
- 1.2 GC解决的三个核心问题
- 1.3 对象存活判定算法
- 二、四大核心垃圾回收算法详解
- 2.1 标记-清除算法(Mark-Sweep)
- 执行过程
- 优点
- 缺点
- 适用场景
- 2.2 标记-复制算法(Mark-Copy)
- 执行过程
- 优点
- 缺点
- 改进:Appel式回收
- 适用场景
- 2.3 标记-整理算法(Mark-Compact)
- 执行过程
- 优点
- 缺点
- 与标记-清除的对比
- 适用场景
- 2.4 分代收集算法(Generational Collection)
- 核心思想
- JVM分代模型
- 对象晋升机制
- GC类型
- 三、算法演进与组合使用
- 3.1 算法组合的基本原则
- 3.2 常见垃圾收集器与算法对应关系
- 3.3 算法性能对比
- 四、关键概念与注意事项
- 4.1 Stop-The-World(STW)
- 4.2 安全点(Safepoint)
- 4.3 垃圾回收的性能指标
- 4.4 常见误区
- 五、总结
- JVM垃圾回收算法 面试高频考点问答清单
- 一、垃圾回收基础概念
- Q1:什么是垃圾回收(GC)?它解决了什么问题?
- Q2:JVM如何判断一个对象是否存活?
- Q3:什么是循环引用?为什么引用计数法无法解决?
- 二、四大核心垃圾回收算法
- Q4:简述标记-清除算法(Mark-Sweep)的执行过程、优缺点和适用场景
- Q5:简述标记-复制算法(Mark-Copy)的执行过程、优缺点和适用场景
- Q6:什么是Appel式回收?它解决了什么问题?
- Q7:简述标记-整理算法(Mark-Compact)的执行过程、优缺点和适用场景
- Q8:标记-清除和标记-整理算法有什么区别?
- 三、分代收集算法
- Q9:什么是分代收集算法?它的核心思想是什么?
- Q10:JVM的分代模型是怎样的?各代采用什么算法?
- Q11:对象什么时候会晋升到老年代?
- Q12:Minor GC、Major GC和Full GC有什么区别?
- 四、关键性能与机制概念
- Q13:什么是Stop-The-World(STW)?为什么会发生?
- Q14:什么是安全点(Safepoint)?
- Q15:垃圾回收的三个核心性能指标是什么?它们之间有什么关系?
- 五、常见误区与调优基础
- Q16:调用System.gc()会立即触发Full GC吗?
- Q17:为什么新生代GC比老年代GC快?
- Q18:标记-整理算法总是比标记-清除算法好吗?
- 六、垃圾收集器与算法对应关系
- Q19:常见垃圾收集器分别采用什么核心算法?
- Q20:现代垃圾收集器(G1、ZGC)还基于分代思想吗?
- JVM垃圾回收算法 一页纸速记版
- 一、基础核心
- 二、四大核心算法(必考)
- 三、分代收集核心
- 四、关键机制
- 五、收集器与算法对应
- 六、常见误区
JVM垃圾回收算法 系统性知识体系总结
一、垃圾回收基础概念
1.1 垃圾回收的本质
垃圾回收(Garbage Collection, GC)是JVM自动管理内存的核心机制,负责识别并释放不再被使用的内存空间,避免内存泄漏和OOM(OutOfMemoryError)。
1.2 GC解决的三个核心问题
- 哪些内存需要回收:判断对象是否存活
- 什么时候回收:触发GC的时机
- 如何回收:垃圾回收算法的实现
1.3 对象存活判定算法
- 引用计数法:给对象添加引用计数器,引用+1,引用失效-1,计数器为0则可回收
- 优点:实现简单,判定效率高
- 缺点:无法解决循环引用问题(JVM未采用)
- 可达性分析算法:以"GC Roots"为起点,向下搜索,不可达的对象即为垃圾
- GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象
- JVM主流实现采用此算法
二、四大核心垃圾回收算法详解
2.1 标记-清除算法(Mark-Sweep)
最基础的垃圾回收算法,其他算法均基于此改进而来
执行过程
- 标记阶段:遍历所有GC Roots,标记所有存活对象
- 清除阶段:遍历堆内存,回收所有未被标记的对象
优点
- 实现简单,不需要移动对象
- 适用于存活对象多、垃圾少的场景
缺点
- 内存碎片化严重:回收后产生大量不连续的内存碎片
- 分配大对象时可能提前触发Full GC
- 标记和清除过程效率都不高(需遍历两次堆)
适用场景
- 老年代垃圾回收的基础
- 早期JVM版本的默认算法
2.2 标记-复制算法(Mark-Copy)
为解决标记-清除的内存碎片化问题而设计
执行过程
- 将内存划分为大小相等的两块区域:From区和To区
- 只使用其中一块(From区)进行对象分配
- 当From区满时,标记所有存活对象
- 将存活对象完整复制到To区,并按顺序排列
- 清空整个From区,交换From和To的角色
优点
- 无内存碎片化:复制后对象连续排列
- 分配内存时只需移动指针,效率极高
- 只需要遍历一次存活对象,垃圾多时效率高
缺点
- 内存利用率低:只能使用一半的内存空间
- 存活对象多时,复制开销大
改进:Appel式回收
- 新生代采用"1个Eden区+2个Survivor区"的划分方式(默认比例8:1:1)
- 每次只使用Eden和一个Survivor区
- 回收时将存活对象复制到另一个Survivor区
- 内存利用率提升至90%
适用场景
- 新生代垃圾回收:因为新生代对象"朝生夕死",存活对象少,复制开销小
2.3 标记-整理算法(Mark-Compact)
结合了标记-清除和标记-复制的优点,解决老年代的内存问题
执行过程
- 标记阶段:与标记-清除相同,标记所有存活对象
- 整理阶段:将所有存活对象向内存一端移动,按顺序排列
- 直接清理掉边界以外的所有内存
优点
- 无内存碎片化:对象连续排列
- 内存利用率100%(不需要预留空间)
- 适合存活对象多的场景
缺点
- 需要移动对象:STW(Stop-The-World)时间更长
- 实现复杂,需要更新所有引用地址
与标记-清除的对比
| 特性 | 标记-清除 | 标记-整理 |
|---|---|---|
| 内存碎片 | 严重 | 无 |
| 对象移动 | 不需要 | 需要 |
| 执行效率 | 中等 | 低 |
| 内存利用率 | 高 | 高 |
| 适用场景 | 存活对象少 | 存活对象多 |
适用场景
- 老年代垃圾回收:老年代对象存活时间长,存活对象多
2.4 分代收集算法(Generational Collection)
不是一种独立的算法,而是基于对象生命周期的组合策略
核心思想
- 根据对象的存活时间将堆内存划分为不同的代
- 不同代采用不同的垃圾回收算法,以提高整体效率
JVM分代模型
新生代(Young Generation)
- 存放新创建的对象
- 特点:对象生命周期短,存活率低
- 回收算法:标记-复制算法
- 回收频率:高,每次回收时间短
- 分为:Eden区(80%)、Survivor0(10%)、Survivor1(10%)
老年代(Old Generation)
- 存放长期存活的对象
- 特点:对象生命周期长,存活率高
- 回收算法:标记-清除或标记-整理算法
- 回收频率:低,每次回收时间长
方法区/元空间(Method Area/Metaspace)
- 存放类信息、常量、静态变量等
- 回收条件苛刻,主要回收废弃常量和无用类
- 回收频率:极低
对象晋升机制
- 对象在Survivor区每熬过一次Minor GC,年龄+1
- 默认年龄达到15岁时晋升到老年代
- 大对象直接进入老年代(避免在新生代频繁复制)
- Survivor区空间不足时,部分对象提前晋升
GC类型
- Minor GC:新生代垃圾回收
- 触发条件:Eden区满
- 特点:速度快,STW时间短
- Major GC:老年代垃圾回收
- 触发条件:老年代空间不足
- 特点:速度慢,STW时间长
- Full GC:整个堆内存的垃圾回收
- 触发条件:老年代满、方法区满、System.gc()显式调用
- 特点:STW时间最长,应尽量避免
三、算法演进与组合使用
3.1 算法组合的基本原则
- 新生代:标记-复制算法(存活对象少,复制开销小)
- 老年代:标记-清除或标记-整理算法(存活对象多,复制开销大)
3.2 常见垃圾收集器与算法对应关系
| 收集器 | 代 | 核心算法 | 特点 |
|---|---|---|---|
| Serial | 新生代 | 标记-复制 | 单线程,简单高效 |
| ParNew | 新生代 | 标记-复制 | Serial的多线程版本 |
| Parallel Scavenge | 新生代 | 标记-复制 | 吞吐量优先 |
| Serial Old | 老年代 | 标记-整理 | 单线程,作为CMS的后备 |
| Parallel Old | 老年代 | 标记-整理 | Parallel Scavenge的老年代版本 |
| CMS | 老年代 | 标记-清除 | 并发低延迟,有内存碎片 |
| G1 | 全堆 | 标记-整理+复制 | 区域化分代式,可预测停顿 |
| ZGC/Shenandoah | 全堆 | 着色指针+读屏障 | 超低延迟,几乎无STW |
3.3 算法性能对比
| 算法 | 标记效率 | 清除/复制效率 | 内存利用率 | 内存碎片 | 适用场景 |
|---|---|---|---|---|---|
| 标记-清除 | 低 | 低 | 高 | 严重 | 存活对象少 |
| 标记-复制 | 低 | 高 | 低 | 无 | 存活对象极少 |
| 标记-整理 | 低 | 极低 | 高 | 无 | 存活对象多 |
| 分代收集 | 高 | 高 | 高 | 低 | 通用场景 |
四、关键概念与注意事项
4.1 Stop-The-World(STW)
- GC执行时,暂停所有用户线程的现象
- 所有垃圾回收算法都会产生STW,只是时间长短不同
- 现代垃圾收集器的核心目标就是缩短STW时间
4.2 安全点(Safepoint)
- 用户线程执行过程中能够暂停的特定位置
- GC只能在安全点触发
- 常见安全点:方法调用、循环跳转、异常跳转
4.3 垃圾回收的性能指标
- 吞吐量:用户代码执行时间/(用户代码执行时间+GC时间)
- 延迟:GC导致的STW时间
- 内存占用:堆内存的使用效率
- 这三个指标构成"不可能三角",无法同时最优
4.4 常见误区
- 误区1:System.gc()会立即触发Full GC
- 实际上只是建议JVM执行GC,JVM可以选择忽略
- 误区2:新生代GC比老年代GC快
- 这是因为新生代存活对象少,而不是算法本身更快
- 误区3:标记-整理总是比标记-清除好
- 当老年代碎片不多时,标记-清除的STW时间更短
五、总结
垃圾回收算法是JVM内存管理的核心,四种算法各有优劣:
- 标记-清除:基础但有碎片
- 标记-复制:无碎片但内存利用率低
- 标记-整理:无碎片且内存利用率高但STW时间长
- 分代收集:结合前三种算法的优点,是现代JVM的主流实现
理解这些算法的原理和适用场景,对于JVM调优和排查内存问题至关重要。随着JVM的发展,新的垃圾收集器(如G1、ZGC)不断涌现,但它们的核心思想仍然基于这四种基础算法。
JVM垃圾回收算法 面试高频考点问答清单
(按面试提问频率排序,答案提炼核心得分点,加粗为必背关键词)
一、垃圾回收基础概念
Q1:什么是垃圾回收(GC)?它解决了什么问题?
答:GC是JVM自动管理堆内存的核心机制,负责识别并释放不再被使用的内存空间。它解决了三个核心问题:
- 哪些内存需要回收(对象存活判定)
- 什么时候回收(GC触发时机)
- 如何回收(垃圾回收算法实现)
Q2:JVM如何判断一个对象是否存活?
答:主流采用可达性分析算法,以"GC Roots"为起点向下搜索,不可达的对象即为垃圾。
- GC Roots包括:虚拟机栈引用的对象、方法区静态属性引用的对象、方法区常量引用的对象、本地方法栈JNI引用的对象
- 淘汰了引用计数法(无法解决循环引用问题)
Q3:什么是循环引用?为什么引用计数法无法解决?
答:两个对象互相引用对方,导致它们的引用计数器永远不为0。例如:
Aa=newA();Bb=newB();a.b=b;b.a=a;a=null;b=null;此时两个对象已无法被访问,但引用计数仍为1,永远无法被回收。
二、四大核心垃圾回收算法
Q4:简述标记-清除算法(Mark-Sweep)的执行过程、优缺点和适用场景
答:
- 执行过程:①标记阶段:遍历GC Roots,标记所有存活对象;②清除阶段:遍历堆,回收所有未标记对象
- 优点:实现简单,不需要移动对象
- 缺点:内存碎片化严重;标记和清除都需要遍历整个堆,效率不高
- 适用场景:老年代垃圾回收的基础,适用于存活对象多、垃圾少的场景
Q5:简述标记-复制算法(Mark-Copy)的执行过程、优缺点和适用场景
答:
- 执行过程:将内存分为大小相等的两块,每次只使用一块;回收时将存活对象完整复制到另一块,然后清空原块
- 优点:无内存碎片化;分配内存只需移动指针,效率极高;垃圾多时效率高
- 缺点:内存利用率低(只能用一半);存活对象多时复制开销大
- 适用场景:新生代垃圾回收(对象朝生夕死,存活少)
Q6:什么是Appel式回收?它解决了什么问题?
答:是标记-复制算法的改进,将新生代划分为1个Eden区+2个Survivor区(默认比例8:1:1)。
- 每次只使用Eden和一个Survivor区
- 回收时将存活对象复制到另一个Survivor区
- 内存利用率从50%提升至90%,是现代JVM新生代的标准实现
Q7:简述标记-整理算法(Mark-Compact)的执行过程、优缺点和适用场景
答:
- 执行过程:①标记阶段:同标记-清除;②整理阶段:将所有存活对象向内存一端移动,按顺序排列;③清理边界外的所有内存
- 优点:无内存碎片化;内存利用率100%
- 缺点:需要移动对象,STW时间更长;实现复杂,需要更新所有引用地址
- 适用场景:老年代垃圾回收(对象存活时间长,存活多)
Q8:标记-清除和标记-整理算法有什么区别?
答:
| 特性 | 标记-清除 | 标记-整理 |
|---|---|---|
| 内存碎片 | 严重 | 无 |
| 对象移动 | 不需要 | 需要 |
| STW时间 | 较短 | 较长 |
| 实现复杂度 | 低 | 高 |
| 适用场景 | 存活对象少 | 存活对象多 |
三、分代收集算法
Q9:什么是分代收集算法?它的核心思想是什么?
答:不是独立算法,而是基于对象生命周期的组合策略。
- 核心思想:根据对象存活时间将堆划分为不同的代,不同代采用最适合的垃圾回收算法,以提高整体效率
- 是现代JVM的主流实现
Q10:JVM的分代模型是怎样的?各代采用什么算法?
答:
- 新生代:存放新创建的对象,特点是生命周期短、存活率低
- 划分:Eden(80%) + Survivor0(10%) + Survivor1(10%)
- 算法:标记-复制算法
- 回收频率:高,每次时间短
- 老年代:存放长期存活的对象,特点是生命周期长、存活率高
- 算法:标记-清除或标记-整理算法
- 回收频率:低,每次时间长
- 元空间:存放类信息、常量、静态变量等,回收条件苛刻,频率极低
Q11:对象什么时候会晋升到老年代?
答:
- 年龄达标:对象在Survivor区每熬过一次Minor GC,年龄+1,默认15岁晋升
- 大对象直接晋升:超过阈值的大对象直接进入老年代(避免新生代频繁复制)
- 动态年龄判定:Survivor区中相同年龄所有对象大小总和超过Survivor空间的一半,年龄≥该年龄的对象直接晋升
- 空间分配担保失败:Minor GC时Survivor区放不下存活对象,直接晋升到老年代
Q12:Minor GC、Major GC和Full GC有什么区别?
答:
- Minor GC:新生代垃圾回收
- 触发条件:Eden区满
- 特点:速度快,STW时间短
- Major GC:仅老年代垃圾回收
- 触发条件:老年代空间不足
- 特点:速度慢,STW时间长
- Full GC:整个堆(新生代+老年代+元空间)的垃圾回收
- 触发条件:老年代满、元空间满、System.gc()显式调用、空间分配担保失败
- 特点:STW时间最长,应尽量避免
四、关键性能与机制概念
Q13:什么是Stop-The-World(STW)?为什么会发生?
答:GC执行时,暂停所有用户线程的现象。
- 原因:GC需要准确标记存活对象,若用户线程同时运行,会导致对象引用关系不断变化,无法准确标记
- 所有垃圾回收算法都会产生STW,现代收集器的核心目标就是缩短STW时间
Q14:什么是安全点(Safepoint)?
答:用户线程执行过程中能够暂停的特定位置。
- GC只能在安全点触发
- 常见安全点:方法调用、循环跳转、异常跳转
- 设计原则:让用户线程不会长时间运行不到安全点
Q15:垃圾回收的三个核心性能指标是什么?它们之间有什么关系?
答:三个指标构成GC不可能三角,无法同时最优:
- 吞吐量:用户代码执行时间/(用户代码执行时间+GC时间),越高越好
- 延迟:GC导致的STW时间,越短越好
- 内存占用:堆内存的使用效率,越低越好
- 吞吐量优先收集器:Parallel Scavenge + Parallel Old
- 延迟优先收集器:CMS、G1、ZGC
五、常见误区与调优基础
Q16:调用System.gc()会立即触发Full GC吗?
答:不会。System.gc()只是建议JVM执行Full GC,JVM可以选择忽略。
- 生产环境中应避免显式调用System.gc(),可能导致不必要的Full GC
Q17:为什么新生代GC比老年代GC快?
答:不是因为标记-复制算法本身更快,而是因为新生代存活对象极少。
- 标记-复制算法只需要处理存活对象,垃圾越多效率越高
- 老年代存活对象多,无论是标记-清除还是标记-整理,都需要处理大量对象,所以速度慢
Q18:标记-整理算法总是比标记-清除算法好吗?
答:不是。
- 当老年代内存碎片不多时,标记-清除的STW时间更短(不需要移动对象)
- 当碎片积累到一定程度,无法分配大对象时,才需要执行标记-整理
- CMS收集器就是平时用标记-清除,碎片过多时用Serial Old做标记-整理作为后备
六、垃圾收集器与算法对应关系
Q19:常见垃圾收集器分别采用什么核心算法?
答:
| 收集器 | 代 | 核心算法 | 核心特点 |
|---|---|---|---|
| Serial | 新生代 | 标记-复制 | 单线程,简单高效 |
| ParNew | 新生代 | 标记-复制 | Serial的多线程版本 |
| Parallel Scavenge | 新生代 | 标记-复制 | 吞吐量优先 |
| Serial Old | 老年代 | 标记-整理 | 单线程,CMS后备 |
| Parallel Old | 老年代 | 标记-整理 | Parallel Scavenge老年代版本 |
| CMS | 老年代 | 标记-清除 | 并发低延迟,有碎片 |
| G1 | 全堆 | 标记-整理+复制 | 区域化分代,可预测停顿 |
| ZGC/Shenandoah | 全堆 | 着色指针+读屏障 | 超低延迟(<10ms) |
Q20:现代垃圾收集器(G1、ZGC)还基于分代思想吗?
答:
- G1仍然基于分代思想,但采用区域化分代,将堆划分为多个大小相等的Region,每个Region可以动态扮演Eden、Survivor或老年代
- ZGC和Shenandoah是不分代的全堆收集器,但它们的核心优化思想仍然源于四大基础算法,特别是标记-整理和复制算法
JVM垃圾回收算法 一页纸速记版
(加粗为必背关键词,考前3分钟快速过)
一、基础核心
- GC本质:自动识别并释放无用内存,解决"哪些/何时/如何回收"
- 对象存活判定:
- 主流:可达性分析(以GC Roots为起点,不可达即垃圾)
- GC Roots:栈引用、静态属性、常量、JNI引用
- 淘汰:引用计数法(无法解决循环引用)
二、四大核心算法(必考)
| 算法 | 核心过程 | 核心优点 | 核心缺点 | 核心适用 |
|---|---|---|---|---|
| 标记-清除 | 标记存活→清除垃圾 | 简单,不移动对象 | 内存碎片严重,效率低 | 老年代基础 |
| 标记-复制 | 分两块→复制存活→清空原块 | 无碎片,分配快 | 内存利用率50%,存活多则复制贵 | 新生代(朝生夕死) |
| 标记-整理 | 标记存活→移到一端→清理边界 | 无碎片,利用率100% | 需移动对象,STW更长 | 老年代(存活多) |
| 分代收集 | 按存活时间分代→各代用最优算法 | 综合效率最高 | 实现复杂 | 现代JVM主流 |
- Appel式回收:新生代8:1:1(Eden+2Survivor),利用率90%,标准实现
三、分代收集核心
- 新生代:Eden(80%)+S0(10%)+S1(10%),标记-复制,Minor GC频繁且快
- 老年代:长期存活对象,标记-清除/整理,Major GC慢且少
- 对象晋升:年龄15岁、大对象、动态年龄判定、空间担保失败
- GC类型:
- Minor GC:Eden满→新生代→STW短
- Full GC:老年代/元空间满→全堆→STW最长,尽量避免
四、关键机制
- STW:GC暂停所有用户线程,所有算法都有,目标是缩短时间
- 安全点:GC只能在此触发(方法调用、循环、异常)
- GC不可能三角:吞吐量、延迟、内存占用无法同时最优
- 吞吐量优先:Parallel组合
- 延迟优先:CMS、G1、ZGC
五、收集器与算法对应
| 收集器 | 代 | 核心算法 | 核心特点 |
|---|---|---|---|
| Serial/ParNew | 新生代 | 标记-复制 | 单/多线程 |
| Parallel Scavenge | 新生代 | 标记-复制 | 吞吐量优先 |
| CMS | 老年代 | 标记-清除 | 并发低延迟,有碎片 |
| G1 | 全堆 | 标记-整理+复制 | 区域化分代,可预测停顿 |
| ZGC | 全堆 | 着色指针+读屏障 | 超低延迟(<10ms) |
六、常见误区
- System.gc()只是建议GC,不会立即执行
- 新生代GC快是因为存活对象少,不是算法本身快
- 标记-整理不总是更好:碎片少时标记-清除STW更短