第67篇:JVM面试压轴题(2026版)
📌 系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第66篇:JIT编译与性能优化 |
➡️ 下一篇:第68篇:JavaWeb核心技术之Servlet
🗺️ 本文阅读地图(3 分钟速览)
第61~66篇完成了JVM调优专题的全部学习,本篇是JVM专题收官之作。精选 35+ 道大厂高频面试题,覆盖 7 大模块,每题配标准话术 + 加分回答:
| 模块 | 核心考点 | 面试权重 |
|---|---|---|
| 内存区域 | 五大运行时区、堆与栈区别、元空间 vs 永久代 | ⭐⭐⭐⭐⭐ |
| 垃圾回收 | GC算法、回收器选型、G1 vs ZGC | ⭐⭐⭐⭐⭐ |
| GC调优 | 调优流程、参数配置、日志分析 | ⭐⭐⭐⭐ |
| 类加载器 | 双亲委派、打破双亲委派、热部署 | ⭐⭐⭐⭐ |
| JIT编译 | 分层编译、逃逸分析、方法内联 | ⭐⭐⭐ |
| OOM排查 | 泄漏类型、排查工具、MAT分析 | ⭐⭐⭐⭐ |
| 调优实战 | 参数配置、容器化JVM、生产问题 | ⭐⭐⭐⭐ |
一、JVM内存模型篇(8题)
Q1:JVM运行时数据区包含哪些区域?JDK 8之后有什么变化?
答案:五大区域——程序计数器、虚拟机栈、本地方法栈、堆、方法区。JDK 8核心变化:方法区实现从永久代(PermGen) 改为元空间(Metaspace) ,元空间使用本地内存而非JVM堆内存,默认无上限(受物理内存限制),永久代受-XX:MaxPermSize限制。
Q2:堆和栈的区别?
答案:堆(Heap)——所有线程共享,存储对象实例和数组,GC管理,线程共享,OutOfMemoryError。栈(Stack)——每个线程私有,存储局部变量、方法参数、对象引用,自动分配释放,线程私有,StackOverflowError。
Q3:对象在堆内存中的分配流程?
答案:新对象优先分配在Eden区;Eden区满时触发Minor GC,存活对象复制到Survivor区(S0/S1);每经历一次GC且存活,年龄+1,年龄达到15(默认) 晋升到老年代;大对象(超过PretenureSizeThreshold)直接进入老年代。
💡 加分:可通过
-XX:MaxTenuringThreshold调整晋升年龄阈值。
Q4:String s = new String(“abc”) 创建了几个对象?
答案:2个。"abc"字面量在字符串常量池(元空间)中创建1个对象;new String()在堆中创建1个对象。s引用存在虚拟机栈的局部变量表中。如果池中已有"abc",则只创建1个堆对象。
Q5:元空间和永久代的区别?
答案:元空间(Metaspace)使用本地内存,默认无上限(受物理内存限制),JDK 8+使用。永久代(PermGen)在JVM堆内存中,受-XX:MaxPermSize限制,JDK 7及之前使用。JDK 8移除永久代,引入元空间,字符串常量池从永久代移至堆,避免永久代OOM。
💡 加分:元空间默认无上限可能导致本地内存耗尽,生产建议配置
-XX:MaxMetaspaceSize。
Q6:程序计数器(PC Register)的作用?为什么不会OOM?
答案:程序计数器记录当前线程执行的字节码指令地址(或行号),是JVM中唯一不会抛出OutOfMemoryError 的内存区域。每个线程独立拥有,内存极小且固定。
Q7:栈帧中包含哪些内容?
答案:栈帧包含四个部分——局部变量表(方法参数和局部变量)、操作数栈(执行运算时的临时数据)、动态链接(将符号引用转为直接引用,支持多态)、方法返回地址(方法执行完后的返回位置)。
Q8:什么情况下会抛出StackOverflowError?什么情况下会抛出OutOfMemoryError?
答案:StackOverflowError——递归调用过深、方法调用层级超出栈容量(-Xss可调栈大小)。OutOfMemoryError: Java heap space——堆内存耗尽(-Xmx过小或内存泄漏)。OutOfMemoryError: Metaspace——元空间耗尽(类加载过多或类加载器泄漏)。OutOfMemoryError: unable to create new native thread——无法创建新线程(线程数超系统限制)。
二、垃圾回收(GC)篇(8题)
Q9:对象如何判定为“垃圾”?
答案:可达性分析算法——从GC Roots根对象出发向下搜索,搜索路径称为引用链。不在引用链上的对象被判定为不可达,可被回收。GC Roots包括:虚拟机栈(栈帧中的局部变量)引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。
💡 加分:可达性分析需在安全点(Safepoint) 执行,所有线程暂停(STW)。
Q10:四种垃圾回收算法的特点?
| 算法 | 原理 | 优缺点 | 适用区域 |
|---|---|---|---|
| 复制算法 | 分成两块,存活对象复制到另一块 | 无碎片,高效;内存利用率50% | 年轻代 |
| 标记-清除 | 标记存活对象,清除未标记 | 无需移动对象;有碎片 | 老年代(CMS) |
| 标记-整理 | 标记存活对象,整理到一端 | 无碎片;移动开销大 | 老年代 |
| 分代收集 | 不同区域使用不同算法 | 综合优势 | 年轻代+老年代 |
Q11:Minor GC、Major GC、Full GC的区别?
答案:Minor GC——年轻代GC,Eden区满时触发,STW短暂(通常<10ms)。Major GC——老年代GC,CMS或G1的Mixed GC,STW稍长。Full GC——整个堆+元空间的GC,STW最长,应尽量避免。Full GC频繁通常意味着内存泄漏或堆不足。
Q12:垃圾回收器有哪些?各版本默认是什么?
答案:六款主要回收器——Serial(单线程,Client模式)、Parallel(多线程并行,JDK 8默认)、CMS(并发,JDK 14已移除)、G1(分区化,JDK 9+默认)、ZGC(着色指针+读屏障,亚毫秒级)、Shenandoah(并发整理)。JDK 8默认Parallel GC,JDK 9+默认G1 GC。
💡 加分:ZGC在JDK 21引入分代ZGC,CPU开销降低,内存占用更小。
Q13:G1 GC的核心设计思想?
答案:G1将堆划分为多个大小相等的Region(默认2048个),每个Region可扮演Eden、Survivor、Old或Humongous角色。通过Garbage-First策略优先回收垃圾最多的Region,基于历史数据建立停顿预测模型,确保GC停顿时间不超过-XX:MaxGCPauseMillis设定值。
Q14:CMS为什么被淘汰?
答案:CMS的缺陷——①内存碎片:标记-清除算法不压缩,碎片严重;②CPU敏感:并发阶段占用CPU资源;③并发模式失败:老年代空间不足时降级为Serial Old;④浮动垃圾:并发清理时新产生的垃圾需下次处理。JDK 9废弃,JDK 14彻底移除。
Q15:ZGC为什么能实现亚毫秒级停顿?
答案:ZGC利用着色指针在64位指针的高位存储对象元数据,配合读屏障,将大部分GC操作并发化。对象转移时通过读屏障拦截访问,实现零停顿的并发转移。ZGC停顿时间<1ms,且不随堆大小增长。
Q16:强引用、软引用、弱引用、虚引用的区别?
答案:强引用——new Object(),永不回收(除非不可达)。软引用——SoftReference,内存不足时回收,适合缓存。弱引用——WeakReference,GC时立即回收,适合WeakHashMap、ThreadLocal。虚引用——PhantomReference,无法通过它获取对象,仅用于对象回收跟踪(如Cleaner)。
三、GC调优篇(5题)
Q17:GC调优的标准流程是什么?
答案:四步闭环——①监控发现:用jstat、VisualVM发现GC异常;②分析根因:分析GC日志,用MAT定位内存泄漏;③参数调整:每次只改1-2个参数;④验证效果:观察GC指标变化,稳定后固化配置。
Q18:G1 GC的核心调优参数有哪些?
答案:-XX:MaxGCPauseMillis——目标停顿时间(最核心,默认200ms),调低→GC更频繁但单次更短。-XX:G1HeapRegionSize——Region大小(1-32MB),大对象多可调大。-XX:InitiatingHeapOccupancyPercent——触发Mixed GC的堆占用阈值(默认45%)。-XX:G1ReservePercent——预留内存(默认10%),防止晋升失败触发FullGC。
Q19:频繁FullGC的常见原因?
答案:①内存泄漏(静态集合、ThreadLocal未remove);②大对象直接进老年代(PretenureSizeThreshold过小);③老年代空间不足(-Xmx过小);④元空间不足(类加载过多);⑤显式调用System.gc()(-XX:+DisableExplicitGC禁用)。
Q20:吞吐量和延迟如何权衡?
答案:吞吐量=用户代码时间/(用户代码+GC时间),延迟=GC停顿时间。追求高吞吐→用Parallel GC(适合批处理);追求低延迟→用G1/ZGC(适合Web/支付)。两者不可兼得。
Q21:容器环境JVM参数有什么特殊要求?
答案:使用-XX:InitialRAMPercentage和-XX:MaxRAMPercentage替代固定-Xmx,让JVM感知容器内存限制。JDK 10+默认开启UseContainerSupport。推荐配置:-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=80.0。
四、类加载器篇(5题)
Q22:双亲委派模型是什么?
答案:类加载器收到加载请求,先委派给父加载器,父加载器无法加载时自己才尝试加载。三层加载器——Bootstrap(根加载器,C++实现,Java中为null)、Extension/Platform(扩展加载器,JDK 9+改名Platform)、Application(应用加载器,加载classpath)。父加载器优先。
Q23:为什么要设计双亲委派模型?
答案:①安全:避免核心类库被篡改(自定义java.lang.String不会被加载);②避免重复加载:确保类的唯一性;③隔离性:不同类加载器加载的同名类被视为不同类型。
Q24:如何打破双亲委派模型?
答案:重写loadClass()方法,不先调用父加载器。典型场景——Tomcat的WebappClassLoader优先自己加载WEB-INF/classes和WEB-INF/lib的类,实现Web应用隔离。JDBC驱动加载(Thread.currentThread().setContextClassLoader())也打破了双亲委派。
Q25:两个类加载器加载的同一个类能互相转换吗?
答案:不能。即使类名相同,不同类加载器加载的类在JVM中视为不同的类型,强转抛ClassCastException。JVM中类唯一标识是“类加载器+全限定名”。
Q26:热部署的原理是什么?
答案:用新的类加载器重新加载目标类,替换旧的引用。旧类加载器在没有引用后会被GC回收,实现不停机更新。关键点:①确保旧实例没有任何引用链可达;②新旧类不混用;③通过接口调用避免类型转换冲突。
五、JIT编译篇(3题)
Q27:什么是JIT编译器?
答案:JIT(Just-In-Time)在运行时将热点字节码编译为本地机器码并缓存,后续直接执行机器码。启动时解释执行保启动速度,运行中JIT持续优化热点代码。Java因此**“越跑越快”** 。
Q28:分层编译的5个层级是什么?
答案:0层:解释执行;1层:C1无profile编译;2层:C1有限profile编译;3层:C1全量profile编译(采集运行时数据);4层:C2极致优化编译。代码从0层开始,热度上升后逐级晋升到4层。
Q29:逃逸分析能带来哪些优化?
答案:逃逸分析判断对象是否逃逸出方法或线程。基于分析结果可实现:①栈上分配(不逃逸对象在栈上,减轻GC);②标量替换(对象拆散为基本类型变量);③锁消除(单线程锁直接移除)。
六、OOM与工具篇(4题)
Q30:OOM有哪几种常见类型?
答案:①Java heap space——堆内存溢出(最常见,内存泄漏或-Xmx过小);②Metaspace——元空间溢出(类加载过多、类加载器泄漏);③Direct buffer memory——直接内存溢出(NIO/Netty使用);④GC overhead limit exceeded——GC回收98%内存却腾不出空间(基本等于内存泄漏);⑤unable to create new native thread——线程数超系统限制。
Q31:生产环境OOM排查的标准流程?
答案:①看日志确认OOM类型;②jstat监控GC状态;③jmap dump生成堆快照,或配置-XX:+HeapDumpOnOutOfMemoryError自动dump;④MAT深度分析堆快照,定位泄漏根源;⑤修复代码并验证。
Q32:常用的OOM排查工具?
答案:jstat(实时GC监控)、jmap(生成堆快照,会STW)、MAT(深度泄漏分析,最专业)、VisualVM(可视化监控)、Arthas(生产在线诊断,阿里开源)。生产必须开启-XX:+HeapDumpOnOutOfMemoryError自动dump。
Q33:如何防止OOM发生?
答案:①生产开启-XX:+HeapDumpOnOutOfMemoryError;②开启GC日志;③合理设置-Xmx和-Xms;④代码避免静态集合无限增长、资源及时关闭、ThreadLocal及时remove;⑤定期压测和review。
🎤 面试官追问陷阱(加分题)
追问1:“MAT分析堆快照时,Path to GC Roots为什么要排除弱引用和软引用?”
👉 弱引用(WeakReference)和软引用(SoftReference)在GC时会被回收,不会造成内存泄漏。只有强引用链才是泄漏的根本原因。排除弱/软/虚引用后,留下的是真正的泄漏根源。
追问2:“-Xms和-Xmx设置相同有什么好处和坏处?”
👉 好处:避免JVM动态扩容的STW开销,提高性能稳定性。坏处:启动时直接占用最大内存,容器环境下可能浪费资源。生产通常建议设置相同(如
-Xms4g -Xmx4g),避免扩容带来的性能抖动。
追问3:“JDK 11的-XX:+UseContainerSupport默认开启,有什么作用?”
👉 让JVM感知容器内存限制,使用
-XX:MaxRAMPercentage代替固定-Xmx。容器内存超限时,JVM不会因超出容器限制而被K8s杀死,同时避免OOM。
七、练习题
-
分析题:一个服务频繁FullGC,老年代使用率持续98%,FGC每小时几百次。可能是什么原因?如何排查?
-
场景题:某应用需要不停机更新代码,但每次热部署后Metaspace持续增长,最终OOM。分析原因和解决方案。
-
代码题:写出生产环境JVM参数,要求:堆4GB、开启G1、目标停顿100ms、OOM自动dump、开启GC日志轮转。
📊 你的学习进度
- 当前:第67篇 / 共108篇 · 进阶篇:JVM调优与故障排查(第61~70篇)
- ✅ 已完成:基础篇44篇 + 第45~67篇
- 📖 正在学:第67篇
- ⏳ 待学习:第68~108篇
👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇
👉 下一篇文章预告
🚀 下一篇:《第68篇:JavaWeb核心技术之Servlet》
内容简介:Servlet生命周期、容器原理、Filter过滤器、Listener监听器、Servlet 3.0+异步处理、与Spring MVC的关系。
👉 JVM调优专题正式收官!JavaWeb专题即将开启!
📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/slyn_2004/article/details/163342677




