JVM 面试题汇总
JVM 是 Java 后端面试中的重点内容之一,常见考察方向包括:JVM 内存结构、对象创建、对象内存布局、垃圾回收、类加载机制、双亲委派、JVM 调优等。
本文按照JVM 内存结构 → 对象 → 类加载 → 垃圾回收 → 垃圾回收器 → JVM 调优的顺序进行整理,适合 Java 后端面试前复习。
一、JVM 基础
1. 什么是 JVM?
JVM,即 Java Virtual Machine,Java 虚拟机。
它是 Java 程序运行的基础环境,主要负责:
- 加载 Java 字节码
- 执行字节码
- 管理内存
- 垃圾回收
- 提供运行时环境
Java 程序的执行过程可以简单理解为:
Java 源代码 ↓ javac 编译 ↓ .class 字节码 ↓ JVM ↓ 机器指令 ↓ CPU 执行JVM 的核心优势之一就是:
Java 程序编译成与平台无关的字节码,由不同平台上的 JVM 负责执行。
因此可以实现:
一次编译,到处运行。
二、JVM 内存结构
2. JVM 运行时数据区有哪些?
JVM 运行时数据区主要包括:
JVM 运行时数据区 │ ├── 程序计数器 │ ├── Java 虚拟机栈 │ ├── 本地方法栈 │ ├── 堆 │ └── 方法区其中:
线程私有
- 程序计数器
- Java 虚拟机栈
- 本地方法栈
线程共享
- 堆
- 方法区
三、程序计数器
3. 什么是程序计数器?
程序计数器可以理解成:
当前线程正在执行的字节码指令地址。
例如:
线程 A PC → 指令 10 线程 B PC → 指令 25每个线程都有自己的程序计数器。
4. 为什么程序计数器是线程私有的?
因为 CPU 在多个线程之间切换时,需要知道:
每个线程上次执行到了哪里。
所以每个线程都需要独立保存自己的指令执行位置。
5. 程序计数器会发生 OutOfMemoryError 吗?
不会。
程序计数器是 JVM 规范中唯一没有规定OutOfMemoryError情况的区域。
四、Java 虚拟机栈
6. 什么是 Java 虚拟机栈?
每个线程都有一个独立的 Java 虚拟机栈。
线程每调用一个方法,就会创建一个:
栈帧 Stack Frame
方法执行完成后,对应的栈帧出栈。
例如:
publicvoidmethodA(){methodB();}publicvoidmethodB(){}执行过程:
methodA() ↓ 栈帧 A ↓ methodB() ↓ 栈帧 B当methodB()执行完成:
栈帧 B 出栈然后methodA()继续执行。
五、栈帧
7. 栈帧中有什么?
一个栈帧主要包含:
栈帧 │ ├── 局部变量表 ├── 操作数栈 ├── 动态链接 └── 方法返回地址8. 局部变量表是什么?
用于保存:
- 方法参数
- 局部变量
- 基本数据类型
- 对象引用
例如:
publicvoidtest(){inta=10;Useruser=newUser();}栈帧中的局部变量表会保存:
a → 10 user → User 对象引用需要注意:
对象本身通常存储在堆中,栈中保存的是对象引用。
六、栈溢出
9. 什么情况下会出现 StackOverflowError?
当线程请求的栈深度超过 JVM 允许的深度时,会出现:
StackOverflowError最典型的例子就是无限递归:
publicvoidtest(){test();}执行过程:
test() ↓ test() ↓ test() ↓ test() ↓ ...不断创建栈帧,最终栈空间耗尽。
七、堆
10. Java 堆是什么?
Java 堆是 JVM 中最大的一块内存区域之一。
主要用于:
存放对象实例和数组。
例如:
Useruser=newUser();通常可以理解为:
栈 │ │ user 引用 ↓ 堆 ┌───────────┐ │ User 对象 │ └───────────┘堆也是:
垃圾回收器主要管理的区域。
八、堆为什么要分新生代和老年代?
对象具有明显的生命周期特征:
大量对象创建后很快就会死亡,少量对象会长期存活。
因此 JVM 通常会根据对象存活时间进行分代管理。
经典的堆结构:
Java Heap │ ├── 新生代 Young Generation │ │ │ ├── Eden │ ├── Survivor │ └── Survivor │ └── 老年代 Old Generation九、对象为什么优先分配到 Eden?
例如:
Useruser=newUser();一般情况下,新创建的对象首先会分配到:
Eden 区。
当 Eden 空间不足时,会触发 Minor GC。
存活下来的对象会进入 Survivor 区,并根据 GC 次数逐渐晋升到老年代。
十、对象晋升老年代
经典的对象晋升过程:
Eden ↓ Minor GC Survivor ↓ 多次 GC 后仍然存活 Survivor ↓ Old对象年龄达到一定阈值后,可以晋升到老年代。
但需要注意:
实际 JVM 的对象晋升策略比简单的“年龄达到 15 次就进入老年代”更加复杂,可能受到动态年龄判定、大对象分配等因素影响。
十一、方法区
11. 什么是方法区?
方法区是 JVM 规范定义的运行时数据区域。
主要用于存储:
- 类信息
- 方法信息
- 字段信息
- 运行时常量池
- 静态变量等
十二、永久代和元空间
12. 永久代和元空间有什么区别?
JDK 8 之前:
方法区 ↓ 永久代 PermGenJDK 8 开始:
方法区 ↓ 元空间 Metaspace最大的区别:
永久代使用 JVM 堆内存,而元空间主要使用本地内存。
所以 JDK 8 中:
Heap ├── Eden ├── Survivor └── Old Native Memory └── Metaspace十三、运行时常量池
13. 什么是运行时常量池?
运行时常量池属于方法区的一部分。
主要用于存放:
- 类和接口的名称
- 字段名称
- 方法名称
- 常量
- 符号引用等
例如:
Stringname="Tom";字符串相关的常量信息会涉及运行时常量池和字符串常量池。
十四、String 常量池
14. 什么是 String 常量池?
为了避免大量创建相同字符串对象,JVM 会维护字符串常量池。
例如:
Strings1="hello";Strings2="hello";通常:
s1 == s2结果为:
true因为两个变量引用的是同一个字符串常量。
15. new String(“hello”) 创建几个对象?
经典面试题:
Strings=newString("hello");通常从概念上可以理解为可能涉及两个对象:
"hello" ↓ 字符串常量池对象 new String(...) ↓ 堆中的 String 对象但具体对象数量要结合字符串常量池中是否已经存在"hello"来判断。
面试时不要机械回答“一定两个”。
十六、对象创建过程
16. new 一个对象发生了什么?
例如:
Useruser=newUser();大致过程:
new ↓ 类加载检查 ↓ 分配内存 ↓ 初始化零值 ↓ 设置对象头 ↓ 执行构造方法 ↓ 返回对象引用可以总结成:
类加载检查 → 分配内存 → 初始化零值 → 设置对象头 → 执行构造方法。
十七、对象内存布局
17. 一个对象在内存中包含什么?
HotSpot 中通常可以从三个部分理解:
对象 │ ├── 对象头 │ ├── 实例数据 │ └── 对齐填充18. 对象头有什么?
对象头主要包含:
Mark Word
用于保存:
- 哈希码
- GC 年龄
- 锁状态
- 锁相关信息等
类型指针
指向对象所属的类信息。
十九、指针压缩
19. 什么是指针压缩?
64 位 JVM 中,普通对象指针可能占用 64 bit。
为了节省内存,HotSpot 提供:
Compressed Oops
即压缩普通对象指针。
在一定条件下可以把对象引用压缩成 32 bit。
这可以减少对象占用的内存,同时提高 CPU Cache 命中率。
二十、垃圾回收
20. 为什么需要垃圾回收?
Java 不需要像 C/C++ 一样手动释放对象内存。
JVM 会自动判断哪些对象已经没有使用价值,然后回收它们占用的内存。
这就是:
Garbage Collection,GC。
二十一、如何判断对象是否可以被回收?
主要有两种思路:
① 引用计数法
每个对象维护一个引用计数器。
对象 A ↑ 引用 1 对象 A ↑ 引用 2当引用为 0 时,可以回收。
但是引用计数无法解决循环引用问题。
例如:
A → B ↑ ↓ └───┘即使外部已经没有引用:
A → B → A引用计数仍然不为 0。
所以 JVM 主要使用:
可达性分析算法。
二十二、可达性分析
21. 什么是可达性分析?
从 GC Roots 开始向下搜索。
如果对象:
从 GC Roots 出发不可达
那么就说明这个对象已经没有被使用的可能,可以被回收。
例如:
GC Roots │ ↓ 对象 A │ ↓ 对象 BA、B 都是可达对象。
而:
对象 C没有任何 GC Root 可以到达,那么 C 就可能成为垃圾。
二十三、什么是 GC Roots?
常见 GC Roots 包括:
- 虚拟机栈中的局部变量引用
- 方法区中的静态变量引用
- 方法区中的常量引用
- JNI 本地方法栈中的引用
- JVM 内部的一些对象
简单理解:
GC Roots 是垃圾回收进行可达性分析时的起点。
二十四、垃圾回收算法
常见垃圾回收算法:
标记-清除 标记-复制 标记-整理二十五、标记-清除
过程:
第一步:标记垃圾对象 第二步:清除垃圾对象例如:
[A][垃圾][B][垃圾][C] ↓ [A][空闲][B][空闲][C]缺点:
容易产生内存碎片。
二十六、标记-复制
把内存划分成两块:
From To存活对象复制到另一块区域。
例如:
From [A][垃圾][B][垃圾] ↓ To [A][B]然后清空 From。
优点:
没有内存碎片。
缺点:
需要额外空间。
所以非常适合:
存活对象较少的新生代。
二十七、标记-整理
首先标记垃圾对象。
然后将存活对象移动到内存的一侧:
[A][垃圾][B][垃圾][C] ↓ [A][B][C][空闲空间]相比标记-清除:
可以减少内存碎片。
常用于老年代。
二十八、Minor GC、Major GC、Full GC
22. 什么是 Minor GC?
主要针对:
新生代。
因为新生代对象死亡率比较高,所以 Minor GC 通常比较频繁。
23. 什么是 Full GC?
Full GC 通常会涉及整个堆以及方法区等区域,成本较高。
因此:
Full GC 过于频繁通常意味着 JVM 存在内存压力或配置问题。
面试时不要简单认为:
“Full GC 就一定等于整个堆完全回收。”
具体行为取决于垃圾收集器和 JVM 实现。
二十九、Stop-The-World
24. 什么是 STW?
STW:
Stop-The-World。
垃圾回收过程中,为了保证对象状态的一致性,JVM 可能暂停应用线程。
例如:
业务线程 ────────暂停────────→ 继续 ↑ │ GCSTW 时间越长:
应用响应延迟越明显。
因此现代垃圾收集器都会尽量降低 STW 时间。
三十、常见垃圾收集器
常见垃圾收集器包括:
Serial ParNew Parallel CMS G1 ZGC Shenandoah实际面试中重点关注:
CMS、G1、ZGC。
三十一、CMS
CMS:
Concurrent Mark Sweep。
主要目标:
降低老年代垃圾回收时的停顿时间。
主要过程:
初始标记 ↓ 并发标记 ↓ 重新标记 ↓ 并发清除其中:
- 初始标记:STW
- 并发标记:与用户线程并发
- 重新标记:STW
- 并发清除:与用户线程并发
CMS 的主要缺点:
使用标记-清除算法,会产生内存碎片。
CMS 已经在现代 JDK 中被淘汰/移除,因此更多作为 JVM 历史知识和面试题出现。
三十二、G1
25. 什么是 G1?
G1:
Garbage First。
G1 将整个堆划分成大量大小相等的 Region:
Heap ┌───┬───┬───┬───┐ │ R │ R │ R │ R │ ├───┼───┼───┼───┤ │ R │ R │ R │ R │ ├───┼───┼───┼───┤ │ R │ R │ R │ R │ └───┴───┴───┴───┘不再严格要求一块区域必须属于 Eden、Survivor 或 Old。
一个 Region 可以根据实际情况扮演不同角色。
26. G1 为什么叫 Garbage First?
G1 会根据 Region 中:
垃圾最多、回收收益最高
的区域优先进行回收。
所以叫:
Garbage First。
三十三、G1 的特点
G1 主要优势:
- 支持大堆内存
- 可预测停顿时间
- Region 化管理
- 可以混合回收年轻代和老年代
因此:
G1 是现代 Java 应用中非常重要的垃圾收集器。
三十四、ZGC
27. ZGC 是什么?
ZGC 是一种低延迟垃圾收集器。
主要目标:
在非常大的堆内存下,仍然保持很低的 GC 停顿时间。
它大量使用并发处理,并通过着色指针、读屏障等技术降低 STW 时间。
适用于:
对延迟非常敏感的大型 Java 应用。
三十五、类加载机制
28. 什么是类加载?
当 JVM 第一次使用某个类时,需要将.class字节码加载到 JVM 中。
整体过程:
加载 ↓ 验证 ↓ 准备 ↓ 解析 ↓ 初始化通常把:
验证 + 准备 + 解析称为连接。
因此完整过程:
加载 → 连接 → 初始化三十六、加载
29. 加载阶段做什么?
主要完成:
- 获取类的二进制字节流
- 将字节流转换为方法区中的运行时数据结构
- 创建对应的 Class 对象
最终:
.class ↓ JVM ↓ Class 对象三十七、验证
验证阶段主要保证:
字节码符合 JVM 规范,不会危害 JVM 的安全。
包括:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
三十八、准备
准备阶段:
为类的静态变量分配内存并设置默认值。
例如:
publicstaticintcount=10;准备阶段可能是:
count = 0而不是:
count = 10真正赋值10的过程属于初始化阶段。
三十九、初始化
初始化阶段才真正执行类中的初始化代码。
例如:
publicstaticintcount=10;最终:
count = 10如果存在:
static{System.out.println("hello");}也会在初始化阶段执行。
四十、类加载器
30. JVM 中有哪些类加载器?
常见的:
Bootstrap ClassLoader ↓ Platform ClassLoader ↓ Application ClassLoaderBootstrap ClassLoader
负责加载 Java 核心类。
例如:
java.lang.*Platform ClassLoader
负责加载平台相关类。
Application ClassLoader
负责加载应用程序 ClassPath 下的类。
四十一、什么是双亲委派?
类加载器收到类加载请求后:
Application ClassLoader ↓ Platform ClassLoader ↓ Bootstrap ClassLoader先让父加载器尝试加载。
父加载器加载不了:
再由子加载器加载所以叫:
双亲委派模型。
四十二、为什么要使用双亲委派?
主要有两个作用。
① 避免类重复加载
父加载器已经加载过:
java.lang.String子加载器就不会再次加载。
② 保证 Java 核心类安全
例如有人自己写:
java.lang.String正常情况下不会让自定义类加载器优先加载这个类。
因此可以防止核心 API 被随意替换。
四十三、什么情况下会打破双亲委派?
典型例子:
Tomcat 类加载机制、SPI 等。
例如 SPI:
JDBC ↓ DriverManager ↓ ServiceLoader ↓ 具体数据库驱动这里存在:
父加载器加载的代码需要调用子加载器加载的实现类。
所以需要使用类似线程上下文类加载器等机制解决。
面试时知道这个例子即可。
四十四、JVM 调优
31. JVM 出现 OOM 怎么排查?
如果出现:
java.lang.OutOfMemoryError: Java heap space可以按照以下思路排查:
出现 OOM ↓ 确认是哪一种 OOM ↓ 查看 JVM 参数 ↓ 查看堆使用情况 ↓ 分析 Heap Dump ↓ 定位大对象 / 内存泄漏 ↓ 优化代码或 JVM 参数常见工具:
jps jstat jmap jstack jcmd VisualVM MAT四十五、常见 JVM 参数
32. 常见堆内存参数有哪些?
例如:
-Xms-Xmx分别表示:
-Xms → 初始堆大小 -Xmx → 最大堆大小例如:
-Xms512m-Xmx2g表示:
初始堆 512 MB,最大堆 2 GB。
33. 为什么一般建议 Xms 和 Xmx 设置成相同?
如果:
Xms < XmxJVM 运行过程中可能需要不断扩展堆。
如果设置:
Xms = Xmx可以减少堆扩容相关的开销,使堆大小更加稳定。
但实际生产环境仍然需要结合具体应用和容器资源合理配置。
四十六、如何排查 CPU 过高?
假设 Java 服务 CPU 突然达到 100%。
可以:
1. top ↓ 找到高 CPU 进程 2. top -H -p PID ↓ 找到高 CPU 线程 3. 线程 ID 转成十六进制 4. jstack PID ↓ 找到对应线程 5. 分析线程正在执行什么代码核心思路:
操作系统定位进程 → 定位线程 → jstack 定位 Java 代码。
四十七、如何排查内存泄漏?
常见思路:
发现内存持续上涨 ↓ 生成 Heap Dump ↓ MAT / VisualVM 分析 ↓ 查看对象数量和占用 ↓ 寻找 GC Roots ↓ 定位异常引用 ↓ 修复代码常见原因:
- 静态集合不断保存对象
- ThreadLocal 使用不当
- 缓存没有设置过期策略
- 监听器没有正确注销
- 集合只增加不删除
四十八、JVM 面试重点总结
如果时间有限,可以优先掌握下面这些。
第一梯队:必须掌握
JVM 内存
- JVM 内存结构
- 堆
- 栈
- 方法区
- 程序计数器
- 元空间
- 栈帧
对象
- 对象创建过程
- 对象内存布局
- 对象头
- String 常量池
- 对象晋升
GC
- GC Roots
- 可达性分析
- 标记-清除
- 标记-复制
- 标记-整理
- Minor GC
- Full GC
- STW
类加载
- 类加载过程
- 类加载器
- 双亲委派
- 为什么需要双亲委派
第二梯队:重点理解
- G1
- CMS
- ZGC
- 元空间
- 指针压缩
- OOM 排查
- CPU 过高排查
- Heap Dump 分析
- JVM 常用参数
第三梯队:了解即可
- TLAB
- 逃逸分析
- 标量替换
- JIT
- 栈上分配
- 字节码执行过程
- GC 日志分析
- Shenandoah
四十九、JVM 知识体系
最后可以把整个 JVM 知识体系总结成:
JVM │ ┌─────────────────┼─────────────────┐ ↓ ↓ ↓ 运行时数据区 类加载机制 执行引擎 │ │ │ ┌────┼────┐ ┌────┼────┐ ┌────┴────┐ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 栈 堆 方法区 加载 双亲 初始化 解释执行 JIT │ │ │ ├── 新生代 │ └── 老年代 │ └── 栈帧 │ ├── 局部变量表 ├── 操作数栈 └── 返回地址 │ ↓ GC │ ┌───────────┼───────────┐ ↓ ↓ ↓ GC Roots 回收算法 垃圾收集器 │ ┌─────┼─────┐ ↓ ↓ ↓ 清除 复制 整理 │ G1 / ZGC五十、JVM 面试最重要的一条主线
如果把 JVM 面试题压缩成一条主线,可以这样理解:
Java 对象 ↓ 创建在哪里? ↓ 堆 ↓ 什么时候被回收? ↓ GC Roots 可达性分析 ↓ 怎么回收? ↓ 标记-清除 / 复制 / 整理 ↓ 谁负责回收? ↓ 垃圾收集器 ↓ 为什么 GC 会暂停程序? ↓ STW ↓ 如何降低停顿? ↓ G1 / ZGC另一条主线:
.class 文件 ↓ 类加载器 ↓ 加载 ↓ 验证 ↓ 准备 ↓ 解析 ↓ 初始化 ↓ Class 对象 ↓ JVM 执行因此,JVM 不应该只靠死记硬背。
真正理解下面这几个问题:
对象放在哪里?
对象什么时候可以被回收?
垃圾怎么回收?
类是怎么加载进 JVM 的?
JVM 出现 OOM、CPU 飙高时怎么排查?
基本就掌握了 JVM 面试的主干知识。
总结
对于 Java 后端实习面试来说,JVM 不需要一开始就钻到 HotSpot 源码。
建议学习顺序:
JVM 内存结构 ↓ 堆和栈 ↓ 对象创建与对象内存布局 ↓ GC Roots + 可达性分析 ↓ 垃圾回收算法 ↓ G1 / ZGC ↓ 类加载机制 ↓ 双亲委派 ↓ JVM 调优与故障排查其中最值得深入理解的是:
JVM 内存结构 + 对象 + GC + 类加载
这四部分是 JVM 面试题的核心。
当面试官继续追问时,再往:
JIT、逃逸分析、TLAB、GC 日志、G1 源码
这些更底层的方向深入即可。