JVM 面试题
本文整理 Java 后端开发高频面试题的 JVM 部分。每道题统一格式:题目 → 答案 → 对比表 / 代码示例 / 一句话记忆。
1. JVM 垃圾回收算法
原题「内部类垃圾回收算法」为笔误,应为「JVM 垃圾回收算法」。
JVM 垃圾回收(GC)主要包含两部分:如何判断对象存活(可达性分析)+ 如何回收(回收算法)。回收算法主要有 4 种:
(1)标记-清除(Mark-Sweep)
分为「标记」和「清除」两个阶段:先标记出所有需要回收的对象,再统一清除。
- 优点:实现简单,不需要移动对象。
- 缺点:会产生大量不连续的内存碎片,分配大对象时可能触发又一次 GC。
- 适用区域:老年代(CMS 收集器采用)。
(2)复制(Copying)
将内存分为两块,每次只用一块,GC 时把存活对象复制到另一块,然后清空当前块。
- 优点:无内存碎片,分配快。
- 缺点:可用内存减半,存活对象多时复制开销大。
- 适用区域:新生代(因为新生代对象朝生夕灭,存活少)。HotSpot 新生代采用 Eden + 2 个 Survivor(8:1:1),浪费仅 10%。
(3)标记-整理(Mark-Compact)
标记过程与标记-清除相同,但后续不是清除,而是让存活对象都向一端移动,然后清理边界以外的内存。
- 优点:无内存碎片,也不浪费空间。
- 缺点:移动对象需要更新引用,开销较大(STW 时间长)。
- 适用区域:老年代(Serial Old、G1 等采用)。
(4)分代收集(Generational Collection)
根据对象存活周期将堆分为新生代和老年代,不同代采用不同算法:
- 新生代:对象存活率低 → 复制算法。
- 老年代:对象存活率高、无额外空间担保 → 标记-清除或标记-整理。
四种算法对比:
| 算法 | 碎片 | 空间利用率 | 性能 | 适用区域 |
|---|---|---|---|---|
| 标记-清除 | 有 | 高 | 中 | 老年代 |
| 复制 | 无 | 低(减半) | 高(存活少时) | 新生代 |
| 标记-整理 | 无 | 高 | 低(移动多) | 老年代 |
| 分代收集 | - | 高 | 优 | 整堆 |
💡 一句话记忆:标记-清除快但有碎片、复制无碎片但费空间、标记-整理无碎片但移动慢、分代收集按代选算法。
2. 什么是双亲委派模型?
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
四种类加载器:
| 类加载器 | 加载范围 | 实现 |
|---|---|---|
| 启动类加载器(Bootstrap) | JAVA_HOME/lib(rt.jar 等) | C++ 实现,JVM 内置 |
| 扩展类加载器(Extension) | JAVA_HOME/lib/ext | Java 实现(ExtClassLoader) |
| 应用类加载器(Application) | classpath(用户类) | Java 实现(AppClassLoader) |
| 自定义类加载器 | 自定义路径 | 继承 ClassLoader |
自定义加载器
↑ 委派
应用类加载器 (AppClassLoader)
↑ 委派
扩展类加载器 (ExtClassLoader)
↑ 委派
启动类加载器 (BootstrapClassLoader)双亲委派的好处:
- 安全性:防止核心类被篡改(例如用户自定义
java.lang.String会被启动类加载器加载真正的 String)。 - 唯一性:保证同一个类只被加载一次,避免类的重复加载。
打破双亲委派的场景:
- SPI 机制(如 JDBC):核心类由启动类加载器加载,但实现类在 classpath 下,启动类加载器无法加载 → 使用线程上下文类加载器(TCCL)委派给应用类加载器。
- Tomcat:每个 Web 应用有自己的类加载器,优先加载自己目录下的类(未先委派父加载器),实现 Web 应用间类隔离。
- OSGi / 热部署:模块化加载,类加载器呈网状结构而非树状。
- JDK 9+ 模块化:平台类加载器替代扩展类加载器,模块化改变了委派机制。
💡 一句话记忆:子先问父能不能加载,父不行子再上;打破它的典型是 SPI(TCCL)和 Tomcat(应用隔离)。