JVM 垃圾回收面试题全解析:从基础到进阶

1. 引言

JVM 垃圾回收(Garbage Collection,简称 GC)是 Java 面试中几乎必考的核心知识点。无论是校招还是社招,面试官都喜欢通过 GC 相关的问题来考察候选人对 JVM 内存管理机制的理解深度。本文整理了 JVM 垃圾回收的高频面试题,从基础概念到进阶原理,帮助你在面试中从容应对。

2. 什么是垃圾回收

垃圾回收(GC)是 JVM 自动管理内存的一种机制。它负责识别并回收堆内存中不再被引用的对象,释放内存空间,避免内存泄漏和内存溢出。

在 Java 中,程序员不需要像 C/C++ 那样手动释放内存,JVM 的垃圾回收器会自动完成这一工作。但这并不意味着我们可以完全忽视内存管理,理解 GC 的工作原理对于写出高性能、低延迟的 Java 应用至关重要。

3. 如何判断对象是否可以被回收

3.1 引用计数法

引用计数法是为每个对象维护一个引用计数器,当对象被引用时计数器加 1,引用失效时计数器减 1。当计数器为 0 时,对象可以被回收。

缺点:无法解决循环引用问题。例如 A 引用 B,B 引用 A,但两者都不再被外部引用时,它们的计数器都不为 0,导致无法被回收。

3.2 可达性分析算法

这是目前主流 JVM(如 HotSpot)采用的算法。它从一组称为 GC Roots 的根对象出发,通过引用链向下搜索,能够到达的对象标记为存活对象,无法到达的对象判定为可回收对象。

GC Roots 包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI 引用的对象
  • 活跃线程(Thread)对象

4. 垃圾回收算法有哪些

4.1 标记-清除算法(Mark-Sweep)

原理:先标记所有需要回收的对象,然后统一回收被标记的对象。

优点:实现简单,不需要移动对象。

缺点:

  • 标记和清除效率都不高
  • 会产生大量不连续的内存碎片,导致后续大对象分配困难

4.2 复制算法(Copying)

原理:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当一块内存用完后,将存活对象复制到另一块,然后清理已使用过的空间。

优点:实现简单,运行高效,不会产生内存碎片。

缺点:内存利用率只有一半,存在空间浪费。

应用场景:HotSpot 虚拟机的新生代采用此算法,将新生代划分为 Eden 区和两个 Survivor 区(默认比例为 8:1:1)。

4.3 标记-整理算法(Mark-Compact)

原理:先标记所有需要回收的对象,然后让所有存活对象向一端移动,最后直接清理掉端边界以外的内存。

优点:不会产生内存碎片,内存利用率高。

缺点:移动对象需要更新引用,效率比复制算法低。

应用场景:老年代通常采用此算法。

4.4 分代收集算法

原理:根据对象存活周期的不同,将堆内存划分为新生代和老年代。新生代对象存活率低,采用复制算法;老年代对象存活率高,采用标记-清除或标记-整理算法。

这是目前主流 JVM 垃圾收集器普遍采用的策略。

5. 什么是分代回收

分代回收是 HotSpot 虚拟机采用的核心策略,它将堆内存划分为以下几个区域:

  • 新生代(Young Generation):存放生命周期短的对象,包括 Eden 区、Survivor 0 区(S0)和 Survivor 1 区(S1)。
  • 老年代(Old Generation):存放生命周期长或经过多次 Minor GC 后仍然存活的对象。
  • 元空间(Metaspace):JDK 8 之后取代永久代,存放类的元数据信息。

新生代 GC(Minor GC):发生在新生代的垃圾回收,频率高、速度快。

老年代 GC(Major GC / Full GC):发生在老年代的垃圾回收,频率低、速度慢,通常伴随至少一次 Minor GC。

6. 常见的垃圾收集器有哪些

6.1 Serial 收集器

单线程收集器,进行垃圾回收时必须暂停所有工作线程(Stop The World)。适用于单 CPU 环境下的客户端模式,简单高效。

6.2 ParNew 收集器

Serial 收集器的多线程版本,使用多条线程并行进行垃圾回收。是服务端模式下首选的新生代收集器之一。

6.3 Parallel Scavenge 收集器

关注点是吞吐量(高效率利用 CPU),适合后台运算而不需要太多交互的任务。JDK 8 默认的新生代收集器。

6.4 Serial Old 收集器

Serial 收集器的老年代版本,单线程,采用标记-整理算法。

6.5 Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本,多线程,采用标记-整理算法。

6.6 CMS 收集器

以获取最短回收停顿时间为目标的收集器,基于标记-清除算法。适合互联网站等对响应时间要求较高的场景。

缺点:

  • 对 CPU 资源敏感
  • 无法处理浮动垃圾
  • 会产生内存碎片

6.7 G1 收集器

面向服务端应用的垃圾收集器,将堆划分为多个大小相等的 Region,可以预测停顿时间。JDK 9 之后成为默认收集器。

特点:

  • 并行与并发
  • 分代收集
  • 空间整合
  • 可预测的停顿时间

6.8 ZGC 收集器

JDK 11 引入的实验性收集器,JDK 15 转正。基于 Region 内存布局,使用染色指针和读屏障技术,停顿时间极短(毫秒级),适用于大堆内存场景。

7. 什么是 Stop The World

Stop The World(STW)是指垃圾回收过程中,JVM 需要暂停所有用户线程,只让垃圾回收线程工作。这是导致 GC 停顿的根本原因。

影响:STW 时间越长,应用的响应延迟越高。因此,现代垃圾收集器的核心优化目标就是缩短 STW 时间。

优化手段:

  • 使用并发收集器(如 CMS、G1)让部分回收工作与用户线程并发执行
  • 使用低停顿收集器(如 ZGC、Shenandoah)

8. 什么是 Minor GC 和 Full GC

Minor GC:发生在新生代的垃圾回收。由于新生代对象大多朝生夕灭,Minor GC 非常频繁,回收速度也很快。

触发条件:Eden 区空间不足时触发。

Full GC:对整个堆(新生代 + 老年代 + 元空间)进行垃圾回收。Full GC 速度慢、停顿时间长,应尽量避免。

触发条件:

  • 老年代空间不足
  • 元空间空间不足
  • 调用 System.gc()(只是建议,不一定会执行)
  • CMS 收集器出现 Concurrent Mode Failure

9. 对象什么时候进入老年代

  • 对象年龄达到阈值 :对象每经历一次 Minor GC 仍然存活,年龄加 1,默认达到 15 岁时进入老年代(可通过 -XX:MaxTenuringThreshold 设置)。
  • 大对象直接进入老年代 :超过 -XX:PretenureSizeThreshold 设置的大对象直接分配在老年代,避免在新生代频繁复制。
  • 动态年龄判定:Survivor 区中相同年龄所有对象大小总和超过 Survivor 区一半时,年龄大于等于该值的对象直接进入老年代。
  • 空间分配担保:Minor GC 后存活对象过多,Survivor 区无法容纳时,多余对象进入老年代。

10. 如何排查内存泄漏和内存溢出

10.1 常见排查工具

  • jps:查看 Java 进程
  • jstat:查看 JVM 统计信息
  • jmap:导出堆转储文件
  • jstack:查看线程堆栈
  • MAT(Memory Analyzer Tool):分析堆转储文件
  • VisualVM:可视化监控 JVM

10.2 排查步骤

  1. 使用 jps 找到目标 Java 进程
  2. 使用 jmap -dump:format=b,file=heap.hprof <pid> 导出堆转储文件
  3. 使用 MAT 或 VisualVM 分析堆转储文件,定位大对象和可疑引用链
  4. 结合代码分析,找到内存泄漏的根源

10.3 常见内存泄漏场景

  • 静态集合类持有对象引用未释放
  • 未关闭的连接(数据库连接、IO 流)
  • 监听器未注销
  • 缓存中的对象未清理
  • ThreadLocal 使用不当

11. 高频面试题汇总

11.1 基础题

Q1:Java 中如何判断对象可以被回收?

答:主要通过可达性分析算法,从 GC Roots 出发,无法到达的对象可以被回收。引用计数法因无法解决循环引用问题,主流 JVM 已不采用。

Q2:GC Roots 包含哪些?

答:虚拟机栈中引用的对象、方法区静态属性和常量引用的对象、本地方法栈 JNI 引用的对象、活跃线程对象。

Q3:新生代为什么采用复制算法?

答:新生代对象存活率低,复制算法效率高且不会产生内存碎片。通过 Eden 和两个 Survivor 区的划分,将内存利用率提升到 90% 左右。

11.2 进阶题

Q4:CMS 和 G1 的区别是什么?

答:CMS 基于标记-清除算法,会产生内存碎片;G1 基于 Region 划分,采用标记-整理算法,空间整合更好。G1 可以预测停顿时间,适合大堆场景。

Q5:什么是 Concurrent Mode Failure?

答:CMS 收集器在并发清理阶段,用户线程仍在运行,如果此时老年代空间不足,会触发 Concurrent Mode Failure,导致退化为 Serial Old 收集器进行 Full GC,停顿时间大幅增加。

Q6:如何减少 Full GC 的频率?

答:合理设置堆大小和新生代比例、避免创建过多大对象、及时释放无用引用、使用合适的垃圾收集器、优化代码减少对象创建。

11.3 场景题

Q7:线上应用频繁 Full GC,如何排查?

答:先通过 jstat -gcutil 查看 GC 频率和堆使用情况,再通过 jmap 导出堆转储,用 MAT 分析大对象和引用链,定位内存泄漏点,最后结合代码优化。

Q8:如何选择垃圾收集器?

答:根据应用场景选择。追求吞吐量选 Parallel Scavenge + Parallel Old;追求低延迟选 G1 或 ZGC;单机小应用选 Serial。JDK 8 默认 Parallel,JDK 9+ 默认 G1。

12. 总结

JVM 垃圾回收是 Java 面试的核心考点,需要掌握的知识点包括:

  • 对象存活判定算法(可达性分析)
  • 三种基本回收算法(标记-清除、复制、标记-整理)
  • 分代收集策略
  • 主流垃圾收集器(Serial、Parallel、CMS、G1、ZGC)
  • STW 机制及优化
  • 内存泄漏排查方法

建议在理解原理的基础上,结合线上调优实践加深记忆。面试时不仅要能回答概念,更要能结合实际场景分析问题。

相关推荐
AINative软件工程1 小时前
LLM 应用的 Observability 三件套:Metrics、Logs、Traces 的生产级接入工程实践
后端·python·架构
geovindu1 小时前
rust: Simple Factory Pattern(续)
后端·设计模式·rust·简单工厂模式·创建型模式
知野小兔1 小时前
JavaScript 数组方法大全(超详细整理)
开发语言·前端·javascript
Wang's Blog1 小时前
Java框架 SpringCloud 快速入门: Ribbon 饥饿加载
java·spring cloud·ribbon
哈__1 小时前
飞牛OS还在手动维护?我用Ansible把重复操作写成了Playbook
开发语言·ansible·php
道尔柯南1 小时前
【QT】从零认识 Qt:跨平台 C++ GUI 开发入门
开发语言·c++·qt
高频因子挖掘机1 小时前
股票分钟 K 线为什么需要关注交易时段?——忽略这一点,回测可能从数据层就偏了
后端·github·api
二喵❥(^_-)1 小时前
接口测试流程的三个核心环节
开发语言·lua
高频因子挖掘机1 小时前
批量请求和循环请求有什么本质区别?从量化数据管道重新理解 API 请求粒度
后端·github·api