Skip to content

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/extJava 实现(ExtClassLoader)
应用类加载器(Application)classpath(用户类)Java 实现(AppClassLoader)
自定义类加载器自定义路径继承 ClassLoader
自定义加载器
    ↑ 委派
应用类加载器 (AppClassLoader)
    ↑ 委派
扩展类加载器 (ExtClassLoader)
    ↑ 委派
启动类加载器 (BootstrapClassLoader)

双亲委派的好处:

  1. 安全性:防止核心类被篡改(例如用户自定义 java.lang.String 会被启动类加载器加载真正的 String)。
  2. 唯一性:保证同一个类只被加载一次,避免类的重复加载。

打破双亲委派的场景:

  • SPI 机制(如 JDBC):核心类由启动类加载器加载,但实现类在 classpath 下,启动类加载器无法加载 → 使用线程上下文类加载器(TCCL)委派给应用类加载器。
  • Tomcat:每个 Web 应用有自己的类加载器,优先加载自己目录下的类(未先委派父加载器),实现 Web 应用间类隔离。
  • OSGi / 热部署:模块化加载,类加载器呈网状结构而非树状。
  • JDK 9+ 模块化:平台类加载器替代扩展类加载器,模块化改变了委派机制。

💡 一句话记忆:子先问父能不能加载,父不行子再上;打破它的典型是 SPI(TCCL)和 Tomcat(应用隔离)。