JVM虚拟机面试题(完整版)
目录
- 1. JVM主要组成部分及作用
- 2. 类加载器
- 2.1 类加载器分类
- 2.2 类加载机制及过程
- 2.3 双亲委派机制
- 3. 运行时数据区
- 4. 本地方法接口
- 5. JVM垃圾回收
- 5.1 垃圾回收机制
- 5.2 对象回收判断算法
- 5.3 垃圾回收算法
- 5.4 Java堆分代模型
- 5.5 垃圾回收器
- 5.6 对象分配与回收策略
- 5.7 对象流转完整过程
- 6. JVM常见异常及排查
- 7. JVM调优
- 7.1 JVM调优步骤
- 7.2 调优核心原则
- 7.3 可视化监控工具
- 7.4 实际项目调优案例
- 8. 高频面试问题
1. JVM主要组成部分及作用
JVM核心分为两大子系统和两大组件:
两大子系统
- 类加载器子系统:负责将
.class字节码文件加载到JVM内存中 - 执行引擎子系统:执行加载后类中的字节码指令,会先编译为机器码再执行
两大组件
- 运行时数据区:JVM内存区域,存储程序运行数据
- 本地方法接口:与
native本地方法交互的接口
2. 类加载器
2.1 类加载器分类
- 启动类加载器(Bootstrap):加载Java核心类库(
rt.jar) - 扩展类加载器(Extension):加载JDK扩展目录下的jar包
- 应用类加载器(App):加载项目
classpath下的类文件 - 自定义类加载器:加载自定义路径的JAR/类文件
2.2 类加载机制及过程
类加载完整流程:加载 → 连接 → 初始化
- 加载:通过类加载器读取
.class文件,加载到JVM内存 - 连接
- 校验:验证字节码文件的安全性、正确性
- 准备:为静态变量分配内存,设置默认初始值
- 解析:将常量池符号引用转换为直接引用
- 初始化:执行静态代码块,为静态变量赋实际值
2.3 双亲委派机制
- 定义:类加载器收到加载请求时,优先委托父类加载器执行,递归向上直到启动类加载器;父类无法加载时,子类才尝试加载
- 核心优势:避免类重复加载,保证核心类安全
3. 运行时数据区
JVM内存模型分为5个区域,按线程共享/私有划分:
| 区域名称 | 作用 | 存储内容 | 线程特性 |
|---|---|---|---|
| 堆(Heap) | JVM最大内存区域,GC主要区域 | 对象实例、数组 | 共享 |
| 方法区 | 存储类元数据 | 类信息、常量、静态变量、编译后代码 | 共享 |
| 虚拟机栈 | 方法执行内存模型 | 局部变量表、操作数栈、方法返回地址 | 私有 |
| 程序计数器 | 记录线程执行字节码行号 | 当前执行指令地址 | 私有 |
| 本地方法栈 | 执行native方法服务 | 本地方法调用信息 | 私有 |
4. 本地方法接口
- 作用:用于JVM与底层操作系统、本地
native方法进行交互 - 场景:调用C/C++实现的底层系统功能
5. JVM垃圾回收
5.1 垃圾回收机制
- 自动回收堆中无引用、不可达的垃圾对象,释放内存
System.gc():手动触发GC(不保证立即执行)finalize():GC回收对象前自动调用的方法
5.2 对象回收判断算法
- 引用计数法
- 给对象添加计数器,引用+1,失效-1;计数器=0可回收
- 缺点:无法解决对象循环引用问题
- 可达性分析算法(JVM默认)
- 以
GC Roots为起点,遍历引用链,不可达对象可回收 - 可作为GC Roots的对象:
- 虚拟机栈中引用的对象
- 方法区静态变量引用的对象
- 方法区常量引用的对象
- 本地方法栈引用的对象
- 以
5.3 垃圾回收算法
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 标记-清除 | 先标记存活对象,再清除垃圾 | 简单 | 产生内存碎片 |
| 复制算法 | 内存分两块,存活对象复制到空块,清空原块 | 无碎片 | 浪费50%内存 |
| 标记-整理 | 标记后将存活对象移到一端,清理边界外内存 | 无碎片 | 移动对象开销大 |
| 分代收集 | 新生代用复制,老年代用标记清除/整理 | 综合效率最高 | 逻辑复杂 |
5.4 Java堆分代模型
新生代(对象存活率低)
- 分区:Eden : From Survivor : To Survivor =8:1:1
新生代分为三个区域,分别是一个 Eden 区和两个 Survivor 区,它们的默认比例是 8:1:1。Eden 区:新创建的对象首先会被分配到 Eden 区。当 Eden 区满时,会触发一次 Minor GC(新生代垃圾回收)。Survivor 区:在 Minor GC 时,Eden 区中存活的对象会被移动到其中一个 Survivor 区。同时,From Space 中之前存活的对象,如果经过这次垃圾回收仍然存活,并且年龄达到一定阈值(默认是 15,可以通过-XX:MaxTenuringThreshold参数调整),会被移动到老年代;如果未达到阈值,则会和 Eden 区移动过来的对象一起被复制到另一个 Survivor 区。之后,两个Survivor 的角色会互换 - 垃圾回收:Minor GC,频率高、速度快
- 算法:复制算法
老年代(对象存活率高)
- 存储:长期存活对象、大对象
年龄阈值:对象在 Survivor 区经过多次 Minor GC 后,年龄达到一定阈值,会被晋升到老年代。大对象直接进入:当创建的对象占用内存超过一定大小(可以通过-XX:PretenureSizeThreshold参数设置)时,会直接在老年代分配内存。Survivor 区空间不足:如果在 Survivor 区中,相同年龄的所有对象大小总和大于 Survivor区空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。 - 垃圾回收:Full GC/Major GC,频率低、速度慢
- 算法:标记-清除 / 标记-整理
元空间(Java8+,替代永久代)
- 永久代(Java 7 及以前)存储内容:主要存储类的元数据信息(如类的结构、方法、字段等)、常量池、静态变量等。永久代的大小在启动时就需要指定,且不能动态扩展。问题:由于永久代的大小是固定的,如果加载的类过多,或者常量池过大,容易导致OutOfMemoryError:PermGenspace异常。元空间(Java 8 及以后)存储内容:同样用于存储类的元数据信息,但与永久代不同的是,元空间使用的是本地内存(Native Memory),而不是 JVM 堆内存。优点:元空间的大小可以动态扩展,只要系统的本地内存足够,就不会出现永久代那样的内存溢出问题。
- 存储:类元数据、常量池
5.5 垃圾回收器
新生代收集器
- Serial:单线程、复制算法、停顿用户线程
- ParNew:Serial多线程版本
- Parallel Scavenge:关注高吞吐量
老年代收集器
- Serial Old:Serial老年代版本,标记-整理
- Parallel Old:多线程、标记-整理
- CMS:最短回收停顿,并发回收
整堆收集器
- G1:跨代收集,兼顾吞吐量与停顿时间
5.6 对象分配与回收策略
- 新对象优先分配在Eden区
- 大对象直接进入老年代
- 长期存活对象晋升老年代(默认年龄阈值15)
- Eden区满 →Minor GC
- 老年代满 →Full GC
5.7 对象流转完整过程
- 创建 → 分配到Eden区
- Eden满 → Minor GC → 存活对象进入Survivor
- Survivor中对象年龄递增 → 达标晋升老年代
- 老年代满 → Full GC回收整个堆
- 无引用 → 被GC彻底回收
6. JVM常见异常及排查
StackOverflowError(栈溢出)
- 原因:无限递归、方法调用层级过深、大量局部变量
- 排查:检查递归逻辑、方法调用链
OutOfMemoryError(堆溢出/OOM)
- 原因:加载数据过大、集合未释放、死循环创建对象、内存参数过小
- 排查:分析dump文件、检查对象引用、优化代码
7. JVM调优
7.1 JVM调优步骤
- 监控:使用JConsole、VisualVM查看GC日志、堆内存快照
- 分析:看日志,判断GC频率、停顿时间是否异常
Minor GC执行时间不到50ms;Minor GC执行不频繁,约10秒一次;Full GC执行时间不到1s;Full GC执行频率不算频繁,不低于10分钟1次; - 调整:修改内存参数、GC收集器、分代比例
- 验证:持续监控,找到最优参数
7.2 调优核心原则
-Xms(初始堆)与-Xmx(最大堆)设置为相同值,减少GC次数- 新生代:老年代默认比例1:2,可根据场景调整
- 高并发场景优先使用并行收集器/G1
- 避免大对象直接进入老年代
7.3 可视化监控工具
- JConsole:JDK自带,监控内存、线程、GC
- VisualVM:功能全面的JVM分析工具
- MAT:分析OOM dump文件,定位内存泄漏
7.4 实际项目调优案例
项目场景:知识图谱工具部署后响应慢、频繁OOM
问题原因:数据导入创建大量临时大对象,堆内存不足
调优过程:
- 增大堆内存:
-Xms4g -Xmx4g - 调整分代比例:
-XX:NewRatio=1(新生代:老年代=1:1) - 启用GC收集器:
-XX:+UseG1GC - 开启dump:
-XX:+HeapDumpOnOutOfMemoryError - 代码优化:使用对象池、重构导入逻辑(CSV导入替代内存操作)
最终方案:代码重构+合理JVM参数,解决OOM问题
8. 高频面试问题
对象一定分配在堆中吗?
不一定。无逃逸对象可在栈上分配;简单对象可通过标量替换拆分存储。Minor GC和Full GC区别?
- Minor GC:新生代回收,速度快、频率高
- Full GC:整堆回收,速度慢、频率低
永久代和元空间区别?
- 永久代:Java7及以前,使用堆内存,固定大小
- 元空间:Java8+,使用本地内存,可动态扩展
CMS收集器特点?
并发收集、低停顿、适用于互联网服务端