第一部分:架构与内存篇(必考)
1.JVM 整体结构及内存模型?(超详细满分背诵版)
答:一、JVM整体四大结构 :JVM由四大核心子系统组成,互相配合完成Java程序运行。
①类加载子系统 :负责读取class字节码,执行加载、验证、准备、解析、初始化,管控类生命周期;
②运行时数据区(内存模型) :JVM内存核心,程序运行期间所有数据、对象、线程存储区域;
③执行引擎 :包含解释器、JIT即时编译器、垃圾回收器,负责执行字节码、优化代码、回收垃圾;
④JNI本地方法接口:连接Java与底层C/C++,调用操作系统硬件资源。
二、JVM内存模型(运行时数据区完整划分)
1、线程私有(随线程创建销毁、无GC)
• 程序计数器:记录字节码执行行号,唯一无OOM区域;
• 虚拟机栈:存放栈帧、局部变量、方法执行信息;
• 本地方法栈:执行Native底层方法。
2、线程共享(全局共享、存在GC、容易OOM)
• Java堆:存放所有对象、数组,GC主战场;
• 方法区(元空间):存放类元信息、常量、静态变量;
• 运行时常量池:存放字面量、符号引用;
• 直接内存:堆外内存,NIO、Netty使用。
三、完整执行流程
Java源码→javac编译→class字节码→类加载子系统加载→存入运行时数据区→执行引擎解释/编译执行→GC回收无用对象→JNI调用底层资源,最终完成程序运行。
四、补充特点
JVM采用栈式架构、字节码跨平台设计、自动内存管理、垃圾回收机制,实现一次编译、到处运行。
2.简述 JVM 四大核心模块?(超详细满分背诵版)
答:HotSpot JVM 底层由四大核心模块构成,四个模块串联协作,完整支撑 Java 程序从加载、内存存储、代码执行、底层调用全过程,是虚拟机最基础骨架。
①类加载子系统 :负责读取磁盘中 Class 字节码文件,严格执行加载、验证、准备、解析、初始化五大阶段;依托双亲委派模型管理类加载顺序,维护类唯一性与安全,管控类从加载、使用到卸载的完整生命周期。
②运行时数据区 :JVM 的内存管理核心,程序运行期间所有数据的存储空间;严格划分线程私有内存与线程共享内存,存放栈帧、对象、元数据、常量、本地内存,也是 GC 回收、OOM 异常产生的核心区域。
③执行引擎 :JVM 的运算执行核心,负责把字节码翻译为机器指令执行;包含解释器、JIT 即时编译器、垃圾回收器。解释器保证启动速度,JIT 优化热点代码提升运行效率,GC 自动回收无效垃圾对象。
④JNI本地方法接口 :Java 连通操作系统的桥梁,专门调用 C/C++ 编写的 Native 底层方法;用于操作系统内存、硬件资源、线程调度、IO 读写,弥补 Java 语言底层操作能力不足的缺陷。
补充联动流程:类加载器载入字节码 → 数据存入运行时数据区 → 执行引擎执行代码 → 必要时通过 JNI 调用系统底层资源,四大模块闭环完成程序运行。
总之:
①类加载子系统:加载 Class 字节码,统一管控类完整生命周期;
②运行时数据区:程序运行所有数据内存存储载体;
③执行引擎:结合解释器与 JIT 编译器执行字节码;
④JNI 本地方法接口:对接调用操作系统底层 C/C++ 原生方法。
3.JVM 运行时数据区分为哪几块?哪些线程私有?哪些共享?(超详细满分背诵版)
答:JVM运行时数据区是Java程序在运行过程中,向操作系统申请的一块独立内存空间,也是JVM内存模型的核心区域。总共分为七大内存区域 ,严格划分为线程私有内存、线程共享内存两大类。
一、线程私有内存(3块)
生命周期跟随线程,线程创建开辟内存、线程销毁立即释放;无垃圾回收,不会发生GC。
①程序计数器 :占用内存极小,用于记录当前线程执行的字节码行号;用于线程切换后恢复执行位置;JVM规范唯一不会发生OOM的内存区域。
②Java虚拟机栈 :每个方法执行都会创建一块栈帧;存储局部变量表、操作数栈、动态链接、方法返回地址;方法执行完栈帧自动出栈销毁;递归过深出现StackOverflowError。
③本地方法栈 :作用与虚拟机栈一致,专门为Native本地方法服务,用于执行操作系统底层C/C++方法。
二、线程共享内存(4块)
随虚拟机启动创建、虚拟机关闭销毁;存在垃圾回收,极易出现OOM,是线上故障重点区域。
①Java堆 :JVM最大的一块内存,唯一存放所有对象实例、数组的区域;也是GC垃圾回收的主战场;划分为新生代、老年代,频繁发生MinorGC、MajorGC。
②方法区(JDK8后为元空间) :用于存放类元信息、类结构、常量、静态变量、即时编译代码;JDK8彻底废除永久代,改用本地物理内存实现元空间。
③运行时常量池 :隶属于方法区,存放编译期字面量、符号引用,运行期间可动态添加常量。
④直接内存 :又称堆外内存,不受-Xmx堆内存限制,不属于JVM堆;常用于NIO、Netty零拷贝,读写速度快,使用不当会出现堆外内存溢出。
三、总结区分
线程私有:程序计数器、虚拟机栈、本地方法栈;
线程共享:堆、方法区、运行时常量池、直接内存。
4.虚拟机栈中栈帧包含什么?(超详细满分背诵版)
答:每一个方法被调用时,都会在Java虚拟机栈中创建一块独立内存空间,这块空间称为栈帧;方法执行结束,栈帧自动出栈销毁。栈帧由五大部分组成,结构如下:
①局部变量表 :存放方法内局部变量、基本数据类型、对象引用地址,编译期确定大小;
②操作数栈 :临时存放计算过程中的操作数、中间运算结果,用于虚拟机算术运算、赋值、跳转;
③动态链接 :运行期间将方法内的符号引用,动态转换为直接内存引用,完成方法绑定;
④方法返回地址 :记录方法执行完毕后,恢复上层方法执行位置,正常退出或异常退出跳转地址;
⑤附加信息 :额外存储异常表、调试信息、栈帧缓存等辅助数据。
补充特点:栈帧是线程私有,栈帧之间相互独立、互不干扰;栈内存不存在垃圾回收,方法结束自动释放,效率极高。
5**.程序计数器为什么不会 OOM?**
答:占用内存极小,仅存储当前线程字节码执行行号,是 JVM 规范里唯一规定不会发生内存溢出的区域。
6.JDK8 为什么删除永久代换成元空间?(超详细满分背诵版)
答:在JDK1.7及之前,方法区实现方式为永久代 ,存放在堆内存中;JDK8彻底废除永久代,改用元空间 实现方法区,直接使用操作系统本地物理内存,是JVM重大优化。
一、永久代存在的致命缺点
①永久代固定大小,上限难调,加载大量类、代理类极易发生PermGen永久代溢出OOM;
②永久代占用堆内存,压缩堆可用空间,造成内存浪费;
③垃圾回收效率极低,类卸载困难,频繁出现内存泄漏;
④底层实现复杂,虚拟机调优困难、容错率低。
二、元空间的优势
①元空间直接使用本地物理内存,不受JVM堆内存-Xmx限制;
②自动动态扩容,无需人为设置固定大小,极少出现元空间OOM;
③类加载、方法、常量、字节码存储效率更高,回收更及时;
④简化JVM底层内存结构,降低调优难度,稳定性大幅提升。
三、总结
废除永久代本质:把类元数据从堆中移出,交给本地物理内存管理,彻底解决永久代溢出、堆浪费、回收低效问题,提高JVM运行稳定性。
7.直接内存是什么?使用场景?
答:又称堆外内存,不属于 JVM 运行时堆内存,不受 - Xmx 参数管控;主要用于 NIO、Netty 框架实现零拷贝,减少内存拷贝开销,提升 IO 读写性能。
第二部分:类加载子系统面试题
1.类加载五大阶段分别做什么?(超详细满分背诵版)
答:类加载是将Class字节码文件加载进JVM内存、并转换为可使用Class对象的全过程,分为加载、验证、准备、解析、初始化 五大核心阶段,其中加载到解析为被动加载,初始化是唯一主动加载阶段。
①加载阶段 :磁盘读取二进制Class字节码流,在内存方法区创建Class类对象;同时生成方法元数据、字段、常量池信息,该阶段是类加载入口,支持自定义类加载器读取字节码。
②验证阶段 :四层安全校验,保证字节码合法、安全、符合JVM规范;包含文件格式验证、元数据验证、字节码逻辑验证、符号引用验证;防止恶意字节码、畸形代码破坏虚拟机运行。
③准备阶段 :为类中静态变量 分配内存空间,并且赋上JVM默认初始值;例如int=0、boolean=false、引用类型=null;不会赋值开发者自定义初始值 ,成员变量不在这里分配内存。
④解析阶段 :在运行期间将代码中的符号引用 (字符串形式、逻辑地址),替换为直接内存引用 (真实内存指针地址);包含类、方法、字段、接口解析,为代码调用建立真实内存映射。
⑤初始化阶段 :类加载最后一步,也是唯一主动触发阶段;执行静态代码块、给静态变量赋予业务自定义初始值;严格保证执行顺序,父类优先于子类、静态代码优先于代码块。
补充重要知识点
1、加载、验证、准备、解析阶段属于被动加载,虚拟机自动执行;
2、初始化阶段必须满足主动触发条件才会执行;
3、五大阶段不可逆、串行执行,保证类加载安全有序。
总之:
加载:读取 class 二进制文件,在内存生成 Class 对象;
验证:四层校验字节码合法性与安全性;
准备:为静态变量分配内存并赋予默认初始值;
解析:将符号引用转换为直接内存引用;
初始化:执行静态代码块,给静态变量赋予自定义赋值。
2.什么是双亲委派模型?执行流程?优缺点?(超详细满分背诵版)
答:一、定义 :双亲委派模型是JVM默认的类加载机制,规定类加载器具备层级父子关系;当类加载器收到类加载请求时,不会自己优先加载,而是向上委托父加载器处理,只有父加载器无法完成加载时,当前加载器才会自行加载该类。
二、四层加载器层级(自上而下)
①启动类加载器(Bootstrap):C++编写,加载JDK核心底层类,如java.lang.*;
②平台类加载器(Platform):加载系统扩展类、公共依赖类;
③应用类加载器(App):默认加载开发者项目自定义代码;
④自定义类加载器:用户手动实现,完成特殊加载逻辑。
三、完整执行流程
①当前类加载器接收类加载请求;
②不会主动加载,向上委托给父加载器;
③父加载器重复委托逻辑,层层向上传递至顶层启动类加载器;
④顶层加载器判断是否能够加载,能加载则直接返回Class对象;
⑤若父加载器全部加载失败,自下而上由当前加载器完成加载。
四、核心优点
①安全防护:防止开发者自定义篡改Java核心类,例如伪造String类;
②保证类唯一性:同一个类在整个虚拟机中仅加载一次,避免重复加载;
③层级隔离:区分系统核心类、扩展类、业务自定义类,结构清晰。
五、底层注意点
双亲委派是逻辑父子关系,并非代码继承关系;底层依靠组合模式实现委派,而非类继承。
3.双亲委派破坏场景?详细原理?(超详细满分背诵版)
答:双亲委派是JVM默认加载规则,但是在特殊业务场景下,为了实现底层功能、容器隔离、热更新,需要打破双亲委派模型,常见三大破坏场景以及原理如下 :
①SPI机制(JDBC驱动)
原理:Java核心包中定义驱动接口,而驱动实现类由第三方厂商提供;启动类加载器无法加载第三方jar包,因此需要反向委托,由应用类加载器加载实现类,打破自上而下的委派规则。
②Tomcat Web容器
原理:Tomcat需要部署多个web项目,不同项目存在同名不同版本Jar包,必须实现类隔离;若使用双亲委派,全局只能加载一份类,会出现版本冲突;因此自定义类加载器优先加载项目内部类,打破双亲委派。
③热部署、热加载(开发框架)
原理:SpringBoot、JRebel等热部署工具,需要不停机替换Class字节码;双亲委派会判定类已加载,不会重复加载;因此自定义加载器主动重写加载逻辑,强制重新加载class,破坏双亲委派。
补充总结
常规情况下遵循双亲委派保证安全;凡是需要类隔离、动态加载、反向加载的场景,都会主动破坏双亲委派模型。
4.什么时候触发类的初始化?
答:使用 new 实例化对象、调用静态变量 / 静态方法、通过反射加载类、子类初始化触发父类初始化、启动程序主类。
5.自定义类加载器怎么实现?底层原理?代码示例?实际用途?(超详细满分背诵版)
答:一、实现方式 :Java 提供抽象类 ClassLoader,开发者自定义类加载器只需要继承 ClassLoader ,重写底层核心方法 findClass() ,不要重写 loadClass()。
①继承 ClassLoader 父类;
②重写 findClass 方法,自定义读取 class 字节码逻辑;
③读取本地磁盘、网络、加密文件中的字节码;
④调用 defineClass() 将字节码转为 Class 类对象。
二、手写代码示例(面试必背极简版)
// 自定义类加载器标准模板
public class MyClassLoader extends ClassLoader {
// 重写findClass,自定义读取字节码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 1、读取磁盘class字节码(可改成加密/网络读取)
byte[] bytes = Files.readAllBytes(Paths.get("D:\\Test.class"));
// 2、将字节码转为Class对象
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("类加载失败");
}
}
}
三、为什么不重写 loadClass?
loadClass() 是双亲委派实现入口,如果重写会直接破坏双亲委派模型;遵循规范只重写 findClass,保证委派机制正常执行。
四、底层执行原理
加载请求进来 → loadClass 判断是否已加载 → 委托父加载器 → 父加载器加载失败 → 回调 findClass → 自定义读取字节码 → defineClass 生成 Class 对象。
五、企业实际用途
①代码加密解密 :对 class 文件加密,防止反编译、源码泄露;
②项目热部署、热加载 :不停机刷新 class,不用重启服务;
③依赖包隔离 :中间件、框架隔离不同版本 Jar,解决类冲突;
④动态加载远程类 :从网络、数据库读取 class 字节码,动态创建类。
六、补充特点
自定义类加载器属于应用层级加载器,优先级最低;每一个自定义加载器拥有独立类命名空间,同一个类可被多次加载、相互隔离。
第三部分:GC 垃圾回收面试题(重中之重)
1.JVM 如何判断对象是否垃圾?循环引用代码演示?(超详细满分背诵版)
答:一、主流判定算法 :HotSpot JVM 采用可达性分析法 判定垃圾对象。以一组名为 GC Roots 的固定对象作为起始根节点,从上至下遍历引用链;若一个对象没有任何引用链连通 GC Roots,则判定为不可达对象,属于垃圾对象,等待GC回收。
二、为什么废弃引用计数法?
引用计数法原理:给对象添加引用计数器,被引用+1、引用失效-1,计数器为0判定为垃圾;
致命缺陷:无法解决对象循环引用 ,导致对象永远无法回收,造成内存泄漏。
三、循环引用代码演示(面试必考)
class Entity{
Entity obj;
}
// 循环引用测试
public class ReferenceTest {
public static void main(String[] args) {
Entity a = new Entity();
Entity b = new Entity();
// 互相引用
a.obj = b;
b.obj = a;
// 切断外部引用
a = null;
b = null;
// 触发GC
System.gc();
}
}
四、代码解释
两个对象互相持有引用,引用计数永远不为0;引用计数法无法回收,而可达性分析法无外部引用链,直接判定两个对象均为垃圾并回收 。
五、补充面试总结
①JVM官方最终方案:可达性分析法;
②淘汰方案:引用计数法(存在循环引用硬伤);
③只要断开外部引用、无GC Roots连通,一律判定为垃圾。
2.GC Roots 包含哪些?详细解释?(超详细满分背诵版)
答:一、定义 :GC Roots 是可达性分析法中作为扫描起点的一类特殊对象;垃圾回收器从GC Roots出发遍历全部引用链,能够遍历到达的对象判定为存活对象,无法遍历到达判定为垃圾对象。
二、四大核心 GC Roots(面试必背)
①虚拟机栈引用(最常见) :当前正在运行方法中,局部变量表引用的对象;例如方法内new的对象、临时引用,线程活跃期间永远存活。
②静态变量引用 :被static修饰的静态变量引用对象;属于类级引用,生命周期跟随JVM,不会随意回收。
③常量池引用 :字符串常量池、运行时常量池中引用的对象;例如String常量,长期常驻内存。
④本地方法栈引用 :Native本地方法调用过程中引用的对象,由操作系统底层持有引用,不会被GC回收。
三、拓展补充(大厂加分项)
①同步锁锁定的对象:被synchronized持有的对象,存在锁引用,属于GC Roots;
②JVM内部引用:Class对象、异常对象、系统类加载器等虚拟机内置对象。
四、面试总结
只要被线程栈、静态变量、常量、Native方法、锁持有引用的对象,全部属于GC Roots;GC不会回收根对象,从根对象遍历存活所有关联对象。
3.四种引用区别?层级关系?代码示例?使用场景?(超详细满分背诵版)
答:Java为了灵活管控对象回收时机,设计四种引用层级 ,强度从高到低排序:强引用 > 软引用 > 弱引用 > 虚引用;不同引用决定对象在GC中的回收优先级,适配不同业务场景。
一、强引用(默认引用)
①特点:最基础引用,只要引用链存在,永远不会被GC回收;哪怕JVM内存溢出也不会回收;
②回收条件:手动置为null,断开引用链;
③代码:Object obj = new Object();
④使用场景:日常业务普通对象、实体类、集合,绝大多数常规对象。
二、软引用(SoftReference)
①特点:内存充足不回收,内存紧张、即将OOM时主动回收;
②回收条件:内存不足触发GC,强制回收软引用对象;
③使用场景:做缓存使用(图片缓存、本地临时缓存、大流量非核心缓存),防止缓存常驻内存造成OOM。
三、弱引用(WeakReference)
①特点:只要发生GC,无论内存是否充足,一律强制回收;生命周期极短;
②回收条件:只要GC执行,立刻回收;
③使用场景:临时缓存、无关紧要数据、防止内存泄漏;典型应用:ThreadLocal、WeakHashMap。
四、虚引用(PhantomReference)
①特点:最弱引用,无法通过引用获取对象实例,形同虚设;必须配合引用队列使用;
②回收条件:随时回收,仅用于监听对象回收状态;
③使用场景:监控对象垃圾回收、堆外内存释放、Netty、NIO底层清理资源。
五、面试总结(必背)
①强引用:常驻内存,断引用才回收;
②软引用:内存不够才回收(做缓存);
③弱引用:只要GC就回收(防泄漏);
④虚引用:只监听、不持有、监控回收。
4.四大 GC 算法优缺点?底层原理?适用场景?面试追问?(超详细满分背诵版)
答:HotSpot虚拟机一共有四大垃圾回收算法 ,包含三大基础算法 + 分代收集算法;基础算法分别为标记清除、标记复制、标记整理,分代收集为工业落地组合算法;没有最优算法,只有最合适的算法,JVM根据对象存活特性搭配使用,是GC面试必考核心。
一、标记清除算法(Mark-Sweep)
①底层原理:分为标记、清除两个阶段。第一阶段从GC Roots遍历,标记所有存活对象;第二阶段遍历堆内存,统一清除所有未标记的垃圾对象;全程不移动、不压缩 对象,原地回收垃圾。
②优点:算法逻辑最简单、实现难度低;无需复制移动存活对象,无对象搬运开销;回收中等大小垃圾速度快。
③缺点:产生大量不连续内存碎片 ;当需要分配大对象、没有连续空闲内存时,会提前触发GC;内存利用率低、空闲内存碎片化严重。
④适用场景:老年代、对象存活率高、回收频率低区域;CMS收集器底层采用标记清除算法。
⑤面试追问:内存碎片过多会造成什么?答:大对象无法分配连续内存,频繁触发FullGC。
二、标记复制算法(Mark-Copy)
①底层原理:将内存区域平均划分为两块大小相等的内存空间,一块为使用区、一块为空闲区;GC时标记存活对象,将全部存活对象一次性复制到空闲区,复制完成后直接清空整块使用区,互换角色。
②优点:回收后内存绝对规整、无任何内存碎片;新生对象分配简单,采用指针碰撞 分配内存,分配速度极快;回收效率极高。
③缺点:永久浪费50%内存空间;对象存活率极高时,复制搬运成本成倍增加,性能暴跌;不适合大内存、高存活对象区域。
④适用场景:新生代、对象存活率极低、频繁创建销毁对象;Serial、ParNew底层采用复制算法。
⑤面试补充:新生代8:1:1布局,本质就是为适配标记复制算法。
三、标记整理算法(Mark-Compact)
①底层原理:分为标记、整理两个阶段。先标记全部存活对象;随后将所有存活对象向内存一端整体平移、紧凑压缩排列,边界以外全部空间判定为垃圾,一次性清空。
②优点:无内存碎片、内存利用率极高;无需预留空闲内存,不浪费内存空间;适合大容量堆内存。
③缺点:需要大规模移动、整理存活对象,移动成本极高;全程STW,停顿时间最长、用户线程阻塞久;运行效率偏低。
④适用场景:老年代、大内存、对象存活率极高;G1、ZGC、Serial Old采用标记整理算法。
⑤面试追问:为什么老年代不用复制?答:老年代存活对象多,复制成本太高、浪费一半内存。
四、分代收集算法(工业通用算法、第四大算法)
①底层原理:结合以上三种基础算法,依据对象存活周期长短,将堆划分为新生代、老年代,不同分代采用适配算法。
②算法搭配:新生代存活率低 → 标记复制 ;老年代存活率高 → 标记清除+标记整理 。
③优点:扬长避短、平衡性能;兼顾回收效率、内存规整、内存利用率。
④缺点:内存结构复杂,需要维护分代边界、晋升阈值。
⑤现状:目前所有商用垃圾收集器,全部基于分代收集算法实现。
五、四大算法终极面试总结(必背口诀)
①标记清除:简单快、有碎片、大对象难受;
②标记复制:无碎片、半浪费、短命对象专用;
③标记整理:无碎片、移动慢、老年代专用;
④分代收集:组合算法、兼顾全部、生产通用。
六、大厂高频追问补充
1、什么是指针碰撞?答:内存规整时,内存指针向后偏移即可分配对象,速度远超空闲列表;
2、什么是空闲列表?答:内存碎片化时,虚拟机维护空闲内存列表,遍历寻找合适空间;
3、为什么新生代不用整理?答:存活对象少、复制比整理更快。
5.分代收集策略原理?为什么要分代?(超详细满分背诵版)
答:一、核心定义 :分代收集是HotSpot最核心垃圾回收思想,依据对象存活生命周期长短、存活概率不同,把堆内存划分为新生代、老年代,采用不同垃圾回收算法针对性回收,平衡回收效率与内存碎片。
二、分代划分与算法搭配
①新生代 :存放临时对象、短期存活对象,对象存活率极低、消亡速度快;采用标记复制算法 ,回收速度快、无内存碎片,适合高频快速GC。
②老年代 :存放长期存活、常驻内存对象,存活率极高、更新频率低;采用标记清除+标记整理算法 ,减少内存浪费,降低复制搬运成本。
三、堆内存区域比例
默认新生代占堆内存1/3,老年代占2/3;新生代划分为1块Eden区、2块Survivor区(8:1:1)。
四、为什么要分代?(面试加分)
①如果不分代,全部使用复制算法:浪费大量内存空间;
②如果不分代,全部使用整理算法:每次GC移动大量对象,停顿极长;
③分代目的:不同对象、不同算法、扬长避短、兼顾性能 。
五、补充总结
短期对象复制回收、长期对象整理回收;没有分代就没有现代高性能GC,分代收集是所有垃圾收集器底层基础。
6.五大主流垃圾收集器对比?适用生产场景?(超详细满分背诵版)
答:Java发展至今一共诞生五大主流收集器,分为串行、并行、并发 三类;没有最好收集器,业务场景决定选型,生产环境必考对比。
一、Serial收集器(串行收集器)
①特点:单线程收集、用户线程全部暂停、STW时间长;
②算法:新生代复制、老年代整理;
③适用:客户端、桌面程序、低配置内存、单机小程序;
④缺点:卡顿严重,线上生产禁用。
二、Parallel Scavenge(并行吞吐量收集器)
①特点:多线程并行GC、着重最大化吞吐量;
②算法:新生代复制、老年代整理;
③优点:吞吐量高、内存利用充分;
④缺点:停顿时间长、延迟差;
⑤适用:后台任务、批量处理、不需要低延迟业务。
三、CMS(并发低延迟收集器)
①特点:用户线程与GC线程并发执行、追求低延迟;
②算法:新生代ParNew、老年代标记清除;
③优点:停顿时间极短、体验流畅;
④缺点:内存碎片、浮动垃圾、CPU占用高;
⑤适用:互联网业务、对响应速度敏感服务,JDK8主流。
四、G1收集器(分区兼顾型)
①特点:堆分多个Region分区、可控制停顿时间、兼顾吞吐量+低延迟;
②算法:复制+整理混合算法;
③优点:无大碎片、可预测停顿、超大堆友好;
④缺点:内存开销大、维护成本高;
⑤适用:JDK9之后默认、中大型分布式项目、4G~16G堆内存。
五、ZGC收集器(超低延迟收集器)
①特点:染色指针、读屏障、几乎全程并发、毫秒级停顿;
②优点:TB级大堆、停顿时间不随堆变大变长、极低卡顿;
③缺点:CPU消耗高、内存利用率一般;
④适用:超高并发、金融、交易、大型分布式、JDK11+企业级项目。
六、面试终极选型口诀(必背)
小程序用Serial、后台批量用Parallel、
互联网低延迟用CMS、中大型项目用G1、
超高并发金融系统直接上ZGC。
7.CMS 执行流程?优缺点?
答:流程:初始标记→并发标记→重新标记→并发清除;
优点:大部分阶段与业务线程并发执行,低延迟停顿;
缺点:产生内存碎片,存在浮动垃圾,无法处理极端高并发场景。
8.G1 收集器原理?核心机制?优缺点?适用场景?(超详细满分背诵版)
答:G1(Garbage-First)是一款面向大内存、低延迟、兼顾吞吐量 的混合式垃圾收集器,JDK9 默认收集器,JDK14 之后彻底废弃 CMS,G1 成为商用主流中间版本收集器。
一、底层核心原理
①分区堆内存 :将整个Java堆物理上划分为多个大小相等的独立区域块,称为 Region(分区),默认2048个;每个Region可以动态归属Eden、Survivor、老年代,无需连续整块内存。
②垃圾优先回收 :G1 维护回收优先级列表,根据Region垃圾占比、回收收益排序,优先回收垃圾最多、释放空间最大的分区,这也是G1名字由来。
③混合回收模式 :一次GC同时回收新生代+部分老年代,不再严格分代隔离;采用复制+整理混合算法,全程保证内存规整。
④可预测停顿模型 :用户可设置最大停顿时间(-XX:MaxGCPauseMillis),G1会严格控制单次GC耗时,不会无限拉长STW时间。
二、四大核心阶段(面试必考流程)
①初始标记:短暂STW,标记GC Roots可达对象;
②并发标记:用户线程并行,遍历全部引用链,标记存活对象;
③最终标记:短暂STW,修正并发期间变动的引用;
④混合回收:优先回收高垃圾Region,复制存活对象、清空垃圾分区。
三、优点
①内存规整:全程使用复制算法,无内存碎片 ;
②停顿可控:支持自定义最大停顿时间,延迟远优于Parallel;
③超大堆友好:适合4G~16G中大型堆内存;
④混合回收:同时处理新生代、老年代,减少FullGC触发概率。
四、缺点
①内存开销高:需要维护大量分区标记表、记忆集,占用额外内存;
②CPU负载高:并发标记、扫描需要占用CPU算力;
③超大堆乏力:超过16G堆内存,停顿控制能力变差,不如ZGC。
五、适用生产场景
①JDK9~JDK16 企业通用版本;
②堆内存4G~16G、需要兼顾吞吐量和低延迟;
③业务允许几十毫秒轻微卡顿、不能接受CMS碎片问题。
六、大厂面试追问总结
1、G1记忆集是什么?答:RememberedSet,记录跨分区引用,避免全堆扫描;
2、G1和CMS最大区别?答:G1分区回收、无碎片、可控停顿;CMS整块回收、有碎片、不可控停顿;
3、G1为什么适合大堆?答:拆分Region,不需要连续大块内存,回收更灵活。
9.ZGC 相比 G1 有什么优势?
答:支持 TB 级超大堆内存,实现毫秒级 GC 停顿,利用染色指针实现并发内存整理,内存利用率更高,几乎无业务卡顿,适配高并发大型分布式服务。
10.MinorGC、MajorGC、FullGC 区别?触发条件?底层特点?生产禁忌?(超详细满分背诵版)
答:JVM按照回收区域、回收范围、触发时机,将GC划分为MinorGC、MajorGC、FullGC三种类型;三者停顿时间、回收范围、执行频率差距极大,生产环境重点管控FullGC。
一、MinorGC(新生代垃圾回收)
①回收范围:只针对Eden区、Survivor新生代区域 ,不触碰老年代、元空间;
②触发条件:Eden区内存占满,新生对象无法分配内存,自动触发;
③底层算法:采用标记复制算法,内存规整、无碎片;
④执行特点:触发频率极高、执行速度极快、STW停顿时间短(毫秒级);
⑤对象晋升:存活对象经过GC筛选,年龄达标晋升老年代;
⑥生产评价:正常业务高频MinorGC属于健康状态,无需优化。
二、MajorGC(老年代垃圾回收)
①回收范围:专门针对老年代 ,一般不会主动回收新生代;
②触发条件:老年代内存达到阈值、大对象存入、对象晋升拥挤;
③底层算法:标记清除/标记整理算法,移动成本高;
④执行特点:触发频率低、停顿时间长、耗时远高于MinorGC;
⑤补充注意:MajorGC发生后,大概率伴随一次MinorGC;
⑥生产危害:频繁MajorGC说明内存结构异常,需要排查内存泄漏。
三、FullGC(全局整堆回收)
①回收范围:新生代+老年代+元空间+堆外内存 ,全局全部扫描回收;
②触发条件:内存担保失败、并发GC失败、元空间爆满、手动调用GC;
③底层算法:混合算法,压缩整理内存,产生长时间STW;
④执行特点:停顿时间极长、阻塞所有业务线程、系统卡顿严重;
⑤产生后果:吞吐量暴跌、接口超时、服务抖动、雪崩风险;
⑥生产红线:生产环境严禁频繁FullGC,正常业务一天不允许超过1~2次 。
四、三者核心区别总结(面试必背表格话术)
①MinorGC:回收新生代、频率高、速度快、无危害;
②MajorGC:回收老年代、频率低、速度慢、轻微危害;
③FullGC:全局回收、阻塞严重、损耗最大、生产大忌。
五、大厂面试追问
问:MajorGC和FullGC有什么区别?
答:MajorGC只回收老年代;FullGC是整堆+元空间全盘回收;HotSpot中MajorGC发生后往往伴随FullGC,日常开发基本视为同一类严重GC。
11.什么情况下会触发 FullGC?生产高频触发原因?(超详细满分背诵版)
答:FullGC 是对整个堆内存、元空间、方法区 进行全局垃圾回收,停顿时间极长、损耗极大,生产环境严禁频繁触发;触发条件分为被动触发、主动触发、特殊触发 三大类,详细场景如下:
一、被动触发(生产最常见、自动触发)
①老年代内存不足 :老年代剩余空间不足以存放晋升对象、大对象,直接触发FullGC;是线上最主要原因。
②对象晋升失败 :新生代存活对象太多,Survivor存放不下、晋升老年代,老年代空间不足导致晋升失败,触发FullGC。
③元空间溢出 :动态生成大量代理类、反射类、Lambda类,元空间占用达到阈值,触发FullGC进行类卸载。
④MinorGC 担保失败 :新生代GC前判断老年代剩余最大连续空间,不足以存放最坏存活对象,提前触发FullGC。
⑤并发GC模式下并发失败 :CMS、G1并发回收期间,业务线程产生垃圾速度超过GC回收速度,导致堆塞满,触发Concurrent Mode Failure,强制退化为带STW的FullGC。
二、主动触发(人为代码触发)
①代码手动调用 System.gc() ,虚拟机建议执行FullGC;
②使用jmap、jconsole等命令手动dump堆快照,强制触发FullGC。
三、特殊触发场景
①JVM方法区、永久代内存达到回收阈值;
②堆内存发生内存溢出前,JVM会优先执行一次FullGC尝试释放空间;
③程序退出前,虚拟机默认执行一次FullGC清理资源。
四、生产环境高频诱发原因(面试加分)
①业务产生大量大对象,直接进入老年代塞满内存;
②内存泄漏导致老年代对象只增不减;
③频繁动态生成Class类(反射、代理、动态脚本)耗尽元空间;
④代码滥用System.gc()手动触发。
五、面试总结
线上90%FullGC来源于:老年代塞满、晋升失败、元空间爆满、并发回收失败;生产必须严格规避手动GC、控制大对象、防止内存泄漏。
第四部分:字节码与执行引擎、JMM
1.解释器和 JIT 编译器区别?
答:解释器逐行翻译字节码执行,项目启动速度快,长期运行效率低;JIT 即时编译器识别热点高频代码,编译成本地机器码缓存执行,启动稍慢,长期运行性能极强。
2.JIT 五大优化手段?
答:逃逸分析、栈上分配、标量替换、方法内联、同步消除。
3.什么是逃逸分析?
答:JIT 编译器优化手段,判断对象作用域是否逃出当前方法;未发生逃逸的对象直接分配在虚拟机栈,方法结束自动销毁,大幅降低堆内存 GC 压力。
4.JMM 内存模型作用?三大特性?底层原理?指令重排?(超详细满分背诵版)
答:一、JMM定义 :JMM(Java Memory Model)Java内存模型,是Java虚拟机规范中定义的一套内存访问抽象规范 ;不是硬件内存,是逻辑抽象模型,专门解决多线程并发内存可见性问题。
二、核心作用
①屏蔽不同操作系统、不同CPU硬件、不同架构的内存读写差异;
②统一Java多线程内存执行规范,实现代码跨平台并发一致性;
③限制编译器、CPU指令重排序,规范主内存、工作内存交互规则;
④从底层保障多线程并发的原子性、可见性、有序性 三大特性。
三、JMM内存结构(主内存+工作内存)
①主内存:共享内存,存放所有成员变量、静态变量,线程共享;
②工作内存:线程私有内存,每个线程独有,存放变量副本;
③交互规则:线程不能直接读写主内存,必须拷贝副本到工作内存操作,写完再刷回主内存。
四、并发三大核心特性(必考详解)
①原子性 :一个操作或多个操作,要么全部执行成功、要么全部不执行,执行过程不可中断;
原生基本赋值具备原子性,i++、i=i+1不具备原子性;可通过synchronized、Lock保证原子性。
②可见性 :一条线程修改共享变量,其他线程能够立刻感知最新修改值;
普通变量存在缓存不可见问题,volatile、synchronized、final保证可见性。
③有序性 :程序代码执行顺序按照代码编写顺序执行;
CPU、编译器为优化性能会进行指令重排序 ,volatile禁止指令重排保证有序性。
五、什么是指令重排序?(面试加分)
①定义:CPU和编译器为提升执行效率,在不改变单线程执行结果前提下,打乱代码编写顺序乱序执行;
②危害:多线程环境下破坏业务逻辑、出现诡异bug;
③禁止方式:volatile底层插入内存屏障,限制指令上下重排。
六、面试总结口诀
JMM规范内存、屏蔽硬件差异;
原子性不可断、可见性实时知、有序性不乱排;
volatile保可见、禁重排、不保证原子性。
5.volatile 关键字原理?三大作用?优缺点?不能保证原子性原因?代码示例?(超详细满分背诵版)
答:一、volatile定义 :volatile是Java并发最轻量级同步关键字,用于修饰共享成员变量;底层依靠硬件内存屏障 、CPU缓存一致性协议,解决多线程可见性、指令重排问题,不具备锁排他性。
二、底层实现原理
①内存屏障 :编译后汇编指令追加lock前缀,插入四层内存屏障,限制指令上下乱序重排;
②CPU缓存一致性 :强制修改数据立刻刷回主内存,其他CPU缓存行失效,重新从主内存读取最新数据;
③无锁机制 :volatile不阻塞线程、不切换线程状态,开销远小于synchronized、Lock。
三、volatile三大核心作用(面试必考)
①保证可见性 :一条线程修改volatile变量,强制刷新主内存,其他线程立刻感知最新值,消除工作内存缓存差异;
②禁止指令重排序 :通过内存屏障禁止编译器、CPU乱序优化,保证代码执行有序性,解决单例模式DCL空指针bug;
③不保证原子性 :只能保证单次读写可见,复合操作(i++、赋值计算)无法保证原子性。
四、为什么不能保证原子性?(面试深挖)
i++分为三步:读取值→运算+1→写入主内存;
volatile只能保证读取、写入可见,无法锁住中间运算步骤 ;多线程同时运算会出现覆盖丢失,导致最终结果不准确。
五、简单代码演示(不可保证原子性)
public class VolatileTest {
// volatile修饰
public static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
// 开启10个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
num++;
}
}).start();
}
// 最终结果永远小于10000,证明不保证原子性
}
}
六、volatile优点
①开销极低、无阻塞、无线程上下文切换;
②底层硬件指令实现,执行速度快;
③解决多线程可见性、重排序问题。
七、volatile缺点
①无法保证复合操作原子性;
②频繁读写强制刷新主内存,CPU总线开销变大;
③不能替代锁、不能保证线程互斥。
八、生产实际使用场景
①状态标记位:线程停止、开关标识(boolean flag);
②双重校验锁DCL单例模式,防止指令重排;
③简单读写、无复合运算的共享变量。
九、面试总结口诀
volatile轻量级、底层屏障;
保可见、禁重排、不保原子 ;
适合开关标识、单例DCL,不可做计数运算。
6.Happens-Before 先行发生原则?八大规则?底层意义?(超详细满分背诵版)
答:一、核心定义 :Happens-Before(先行发生原则)是JMM内存模型底层判定规则 ;是Java官方规定的、无需依靠volatile、synchronized就能天然保证的可见性、有序性规则;只要满足先行发生规则,前面代码执行结果对后面代码可见,禁止指令重排序。
二、核心作用
①弱化锁使用,降低并发编程难度;
②规定代码执行先后顺序,禁止非法指令重排;
③保证多线程之间内存数据天然可见性;
④是JMM底层判断重排是否合法的唯一依据。
三、八大必考 Happens-Before 规则(面试必背)
①程序顺序规则 :单线程内,书写在前的代码先行发生于书写在后的代码;单线程永远不会颠倒逻辑性代码。
②锁规则 :解锁操作先行发生于后续对同一把锁的加锁操作;保证锁竞争顺序、保证同步代码块可见性。
③volatile规则 :volatile写操作先行发生于后续对该变量的读操作;底层内存屏障实现。
④线程启动规则 :Thread.start()启动方法先行发生于线程内所有代码;主线程启动子线程,子线程能看见主线程修改值。
⑤线程终止规则 :线程内部所有操作先行发生于线程终止检测;主线程join等待子线程结束,可拿到最新结果。
⑥线程中断规则 :interrupt()中断调用先行发生于中断检测方法。
⑦对象销毁规则 :对象初始化构造完成先行发生于finalize销毁方法。
⑧传递性规则 :A先行B、B先行C,则A一定先行C;传递保证可见性。
四、通俗易懂总结
Happens-Before 不是代码执行顺序,而是可见性+重排合法性判定规则 ;
只要满足规则:前操作结果对后操作可见、禁止乱序重排;
不满足规则:虚拟机可以随意重排、多线程出现不可见诡异bug。
五、面试高频追问
问:volatile底层依靠什么保证有序性?
答:依靠内存屏障 + Happens-Before volatile写前读规则双重约束。
问:为什么单线程不会出现重排bug?
答:程序顺序Happens-Before规则保护,保证单线程逻辑永远正确。
第五部分:调优、线上故障、OOM 面试题
1.生产环境为什么 Xms 和 Xmx 设置相等?
答:避免 JVM 运行过程中频繁进行堆内存动态扩容与缩容,减少内存调整带来的性能损耗与 GC 波动,稳定服务运行状态。
2.线上 JVM 调优核心目标?
答:优先保障业务低响应延迟、提升系统整体吞吐量、严格控制 Full GC 触发频率、平稳控制内存占用避免内存溢出。
3.内存泄漏和内存溢出区别?
答:内存泄漏:无用对象持续持有引用无法被 GC 回收;内存溢出:内存空间耗尽无法创建新对象直接报错;长期内存泄漏最终必然引发内存溢出。
4.常见五种 OOM 原因?报错信息?代码示例?生产场景?解决方案?(超详细满分背诵版)
答:OOM(OutOfMemoryError)内存溢出,指JVM没有足够内存分配对象,抛出内存溢出异常;生产高频出现五大OOM类型 ,每一种成因、报错、排查方式不同,全部面试必考,详细总结如下:
一、Java堆内存溢出(最常见)
①报错:java.lang.OutOfMemoryError: Java heap space
②成因:堆内创建大量对象、存在内存泄漏,对象长期存活无法GC回收,堆满后溢出;
③常见场景:集合无限add、循环创建对象、静态集合常驻引用;
④解决:排查内存泄漏、减少大对象、优化集合引用、适当调高堆内存。
二、元空间内存溢出
①报错:java.lang.OutOfMemoryError: Metaspace
②成因:动态生成大量Class类、反射、CGLIB代理、Lambda、动态脚本,元空间存放类元数据耗尽;
③常见场景:频繁热部署、大量代理类、反射框架滥用;
④解决:调高元空间上限-XX:MaxMetaspaceSize,优化动态类生成频率。
三、堆外直接内存溢出
①报错:java.lang.OutOfMemoryError: Direct buffer memory
②成因:NIO、Netty使用DirectByteBuffer申请堆外内存,不手动释放、内存累积泄漏;
③常见场景:网络IO、文件传输、缓冲区不回收;
④解决:手动调用clean()释放堆外内存,限制直接内存大小。
四、线程溢出(无法创建新线程)
①报错:java.lang.OutOfMemoryError: unable to create new native thread
②成因:无限创建非守护线程、线程池无上限、操作系统线程资源耗尽;
③常见场景:循环new Thread、线程池参数配置错误、死循环创建连接;
④解决:使用线程池、限制最大线程数、销毁闲置线程。
五、超大连续数组溢出
①报错:java.lang.OutOfMemoryError: Requested array size exceeds VM limit
②成因:一次性申请超大连续内存数组,JVM无法分配整块连续空间;
③常见场景:一次性读取超大文件、批量一次性加载海量数据;
④解决:分片读取、流式处理、拆分大数组。
六、面试总结口诀(必背)
堆溢出:对象太多泄漏;
元空间:代理类泛滥;
堆外内存:NIO不释放;
线程溢出:无限建线程;
数组溢出:一次性超大集合。
七、线上通用排查流程
查看报错类型→导出dump快照→MAT分析大对象→定位泄漏代码→优化引用+调整JVM参数。
5.线上 CPU 飙高排查流程?常见原因?解决方案?(超详细满分背诵版)
答:线上CPU飙高是生产最常见故障,会导致接口卡顿、吞吐量下降、服务超时、触发熔断降级;排查遵循**从系统→进程→线程→代码**自上而下流程,全套命令+排查步骤面试必背。
一、标准线上排查流程(面试默写完整版)
①查看服务器总体CPU :执行 top 命令,查看整机CPU占用,定位占用CPU最高的Java进程PID;
②查看进程线程状态 :执行 top -H -p 进程PID,查看该进程下所有线程,找出CPU占用最高的线程TID;
③转换十六进制 :将高负载线程ID转为16进制(jstack底层采用十六进制记录线程);
④导出线程堆栈 :执行 jstack 进程PID | grep 十六进制TID -A 50,精准打印异常线程堆栈;
⑤定位代码 :根据堆栈信息锁定异常类、异常方法,判断死循环、死锁、复杂计算;
⑥修复优化 :修改代码、重启服务、压测验证、上线修复。
二、Java项目四大CPU飙高原因(生产高频)
①代码死循环 :while、for无限循环,无休眠、无阻塞,CPU持续拉满;
②频繁GC :内存泄漏、大量临时对象,频繁MinorGC/FullGC,GC线程抢占CPU;
③线程死锁/自旋锁 :大量线程竞争锁、无限自旋,CPU空转消耗资源;
④复杂逻辑运算 :大数据量循环遍历、递归、复杂算法、JSON序列化解析耗时严重。
三、典型死循环代码示例(面试举例)
// 无休眠死循环,直接导致CPU100%
while(true){
// 空循环、无阻塞、无sleep
}
四、配套排查命令汇总(面试必背)
①top:查看服务器进程资源占用;
②top -H -p pid:查看进程内线程CPU占用;
③jstack:导出线程堆栈,定位卡死代码;
④jstat -gc:判断是否因频繁GC导致CPU升高。
五、通用解决方案
①修复业务死循环、不合理递归;
②加锁优化、减小锁粒度、避免自旋空转;
③优化大数据遍历逻辑,增加分页、异步处理;
④排查内存泄漏,减少无效对象,降低GC频率;
⑤针对耗时代码加缓存、异步、降级限流。
六、面试总结口诀
top找进程、线程排序找TID;
转十六进制、jstack查堆栈;
死循环、频繁GC、死锁最常见;
改代码、控循环、优化逻辑降负载。
6.线上频繁 FullGC 怎么排查?完整排查流程?常见诱因?优化方案?(超详细满分背诵版)
答:频繁FullGC是生产最严重故障,表现为服务卡顿、接口超时、吞吐量暴跌、CPU飙升;排查严格遵循监控观测→命令排查→dump分析→定位代码→上线优化 流程,全套面试默写标准答案,详细如下:
一、标准线上排查流程(面试必背完整版)
①实时监控GC状态 :执行 jstat -gc 进程PID 1000,每秒刷新GC数据;观察FGC次数、FGC耗时、元空间、老年代内存涨幅,判断是否持续性频繁FullGC。
②查看堆内存对象分布 :执行 jmap -heap PID,查看新生代、老年代、元空间内存占用,判断哪一块内存爆满。
③导出堆快照文件 :内存爆满时刻执行 jmap -dump:format=b,file=xxx.dump PID,保存线上堆镜像,用于事后分析。
④MAT工具解析dump :导入MAT内存分析工具,查看大对象、泄漏对象、重复集合、引用链,定位无法回收的异常对象。
⑤查看线程堆栈 :使用jstack排查是否存在线程阻塞、死锁、大量异步任务堆积导致对象暴涨。
⑥定位代码根源 :排查集合未清空、静态集合常驻引用、大对象频繁创建、内存泄漏、动态生成类过多。
⑦临时恢复+长期优化 :紧急重启服务临时恢复,修改代码+调整JVM参数根治问题。
二、线上八大高频诱发FullGC原因(生产99%出处)
①内存泄漏 :集合add不删除、静态集合持有大量对象、ThreadLocal未remove;
②大对象过多 :一次性加载大数据、超大字符串、大数组直接进入老年代;
③对象晋升过快 :新生代过小、存活对象多,频繁晋升老年代导致堆满;
④元空间耗尽 :频繁反射、代理、热部署,生成大量Class类;
⑤手动调用GC :代码、第三方框架滥用System.gc();
⑥担保失败 :MinorGC前老年代连续空间不足,提前触发FullGC;
⑦CMS并发失败 :回收速度赶不上对象产生速度,退化STW FullGC;
⑧堆外内存溢出 :Netty、NIO未释放堆外内存,触发底层FullGC。
三、关键排查命令汇总(面试默写)
①jstat -gc PID:实时查看GC频率、停顿时间;
②jmap -heap PID:查看堆内存分区占用;
③jmap -dump:导出dump快照,离线分析;
④jstack PID:排查线程堆积、死锁。
四、通用解决方案(生产落地)
①修复内存泄漏:集合及时清空、ThreadLocal使用后remove、消除无效引用;
②限制大对象:大数据分片读取、禁止一次性加载海量数据;
③优化JVM参数:调大新生代、设置合理晋升年龄、加大元空间上限;
④禁用手动GC:添加JVM参数 -XX:+DisableExplicitGC;
⑤更换收集器:老旧项目CMS改为G1,降低FullGC概率;
⑥定时清理:定时清空本地缓存、闲置连接、无效集合。
五、面试总结口诀
jstat看GC、jmap查堆区;
dump导出快照、MAT找泄漏;
泄漏、大对象、元空间爆满最常见;
清引用、调参数、分片处理治根源。
7.Docker 容器 JVM 调优注意什么?
答:禁止固定写入过大 - Xmx 堆参数,需适配容器分配内存配额,防止 JVM 超出容器限制被系统直接杀死。
第六部分:工具链面试题(生产必问)
1.jmap、jstack、jstat、jcmd 四大命令区别?常用参数?生产实战场景?(超详细满分背诵版)
答:JDK自带四大线上诊断命令行工具,全部位于jdk/bin目录,无需额外安装;生产排查JVM故障**必用三件套:jstat、jstack、jmap**,jcmd为全能整合工具;四者分工明确、互不冲突,详细深挖如下:
一、jstat(GC实时监控工具)
①核心作用:实时监控JVM运行状态,专门查看内存变化、GC频率、类加载数量,**无侵入、最轻量**;
②常用命令:jstat -gc PID 1000 每秒打印一次GC汇总数据;
③监控指标:新生代、老年代、元空间占用、YGC、FGC次数、GC耗时;
④生产场景:判断是否频繁GC、内存是否持续上涨、定位内存泄漏前兆。
二、jmap(堆内存快照工具)
①核心作用:专门分析堆内存,查看对象分布、内存占用、导出dump快照;
②常用参数:
-heap:查看堆内存整体配置、分区占用;
-dump:导出堆快照,用于MAT离线分析;
-histo:查看当前堆中对象数量、占用大小;
③缺点:执行dump时会**短暂STW**,线上禁止频繁执行;
④生产场景:排查大对象、内存溢出、内存泄漏、死对象堆积。
三、jstack(线程堆栈排查工具)
①核心作用:导出当前虚拟机所有线程堆栈信息,定位线程状态、代码阻塞位置;
②排查问题:线程死锁、死循环、线程阻塞、CPU飙高、线程堆积;
③常用命令:jstack PID 打印全部线程堆栈;
④关键线程状态:BLOCKED阻塞、WAITING等待、RUNNABLE运行、DEADLOCK死锁;
⑤生产场景:CPU100%、接口卡死、线程池积压、死锁排查。
四、jcmd(全能整合工具)
①核心作用:JDK8之后推出,整合上面所有命令功能,一款工具全包;
②优势:低侵入、性能损耗极低,官方推荐替代jmap/jstack;
③生产用途:线上导出堆栈、dump快照、GC统计、线程采样。
五、四者区别(面试必考总结)
①jstat:看**动态GC走势**,监控内存涨跌;
②jmap:看**堆内存对象**,排查OOM内存泄漏;
③jstack:看**线程堆栈**,排查卡顿、死锁、CPU飙高;
④jcmd:全能工具,替代前三款,线上最优选择。
六、线上排查标准使用顺序
jstat观察内存GC走势 → jstack排查线程异常 → jmap导出dump快照 → MAT分析泄漏源头。
2.内存泄漏使用什么工具?完整排查流程?常见泄漏场景?定位特征?(超详细满分背诵版)
答:内存泄漏指无用对象长期被引用、无法被GC回收,内存持续上涨,最终诱发OOM;生产有固定工具栈+标准排查流程,面试必背,详细完整版如下:
一、常用内存泄漏排查工具
①MAT(Memory Analyzer Tool) :官方首选,解析dump堆快照,分析大对象、泄漏对象、引用链、支配树,定位内存泄漏根源;
②JProfiler :可视化专业监控,实时查看对象创建、存活、引用关系,适合开发环境调试;
③Arthas :阿里开源线上诊断,无侵入查看对象数量、类实例个数,实时监控内存上涨;
④VisualVM :JDK自带,免费可视化监控,导出dump、实时查看线程与堆内存。
二、线上标准排查流程(面试默写完整版)
①观察监控 :通过Prometheus+Grafana观察堆内存,判断内存是否只涨不跌、GC后无法释放;
②导出dump快照 :内存最高点使用jmap命令导出堆快照,保留故障现场;
③MAT加载分析 :导入dump文件,查看Histogram直方图,筛选实例数量异常暴涨类;
④查看支配树 :找出占用内存最大的大对象、集合对象;
⑤分析引用链 :查看无法回收对象的GC引用链,定位是谁一直持有引用;
⑥代码溯源修复 :修改代码断开无效引用、手动清除集合、释放资源;
⑦压测验证 :修复后压测,观察内存是否平稳、是否正常回落。
三、生产四大高频内存泄漏场景(必考)
①集合泄漏 :List、Map静态集合无限add,永不clear、不移除;
②ThreadLocal泄漏 :使用后不remove,线程复用导致对象永久滞留;
③连接未关闭 :数据库连接、IO流、Socket连接不关闭,资源常驻内存;
④匿名内部类 :内部类持有外部强引用,导致外部对象无法回收。
四、内存泄漏典型特征
①每次FullGC后内存下降极少,堆内存居高不下;
②老年代内存持续缓慢上涨,无回落拐点;
③系统运行越久越卡顿,最终必然OOM;
④线程数量只增不减,资源连接堆积。
五、面试总结口诀
泄漏内存只涨不落、GC回收不干净;
dump快照加MAT、引用链找源头;
集合、ThreadLocal、连接、内部类、四大泄漏重灾区。
3.Arthas 生产用来做什么?核心命令?实战场景?优势?(超详细满分背诵版)
答:一、Arthas定义 :Arthas是阿里开源、无侵入、无需重启服务的Java线上诊断神器;专门用于线上生产环境排查疑难bug、性能卡顿、代码异常,不需要修改代码、无需停机,是后端工程师线上排查必备工具。
二、核心优点
①无侵入:无需改动业务代码、无需重启服务、不修改配置;
②低损耗:监控性能极低,不会造成服务卡顿;
③功能全面:涵盖类、方法、线程、内存、日志、异常全套排查能力;
④简单易用:命令通俗易懂,线上快速上手排查故障。
三、生产五大核心实战功能(必考)
①查看方法执行耗时 :使用trace命令,追踪接口、方法内部执行链路,精准定位慢方法、慢SQL、卡顿代码;
②实时监控方法调用 :watch命令监控方法入参、出参、异常、返回值,线上不用打日志查看数据;
③反编译线上代码 :jad命令反编译运行中class文件,确认线上是否为最新代码、是否存在代码未更新;
④线程卡死排查 :thread命令查看线程状态,排查死锁、死循环、阻塞线程、CPU飙高源头;
⑤线上热更新代码 :redefine命令不停机修复简单bug,临时紧急修复线上问题,无需重启服务。
四、高频必考生产命令(面试默写)
①trace:追踪方法调用链路,定位慢代码;
②watch:监听方法入参、返回值、异常;
③jad:反编译线上class源码;
④thread:查看线程堆栈、死锁、卡死线程;
⑤heapdump:导出线上堆快照,用于内存泄漏分析。
五、生产使用场景
①线上接口卡顿、响应慢,找不到慢代码;
②线上报错无日志、缺少入参出参记录;
③怀疑代码未部署、线上代码不一致;
④线程死锁、CPU飙高、线程堆积;
⑤紧急修复小bug,不允许重启服务。
六、面试总结口诀
Arthas线上诊断、无侵入不用重启;
trace查卡顿、watch看参数、jad反编译;
排查死锁、热更新、线上疑难bug万能工具。
4.怎么实时监控线上 JVM?技术架构?监控指标?告警规则?部署流程?(超详细满分背诵版)
答:线上JVM监控采用SpringBoot监控技术栈 ,主流组合:Actuator + Prometheus + Grafana + 告警推送 ;实现无侵入采集指标、可视化大屏、异常实时告警,是目前互联网企业标准线上监控方案,全套面试默写标准答案如下:
一、四大组件架构分工
①Actuator(指标暴露) :SpringBoot内置监控组件,无需额外开发;暴露JVM内存、GC、线程、请求、磁盘等监控端点,对外提供metrics指标接口;
②Prometheus(指标采集存储) :时序数据库,定时拉取Actuator监控指标,持久化存储时间序列数据,支持快速查询历史监控走势;
③Grafana(可视化大屏) :搭配Prometheus渲染炫酷监控面板;展示堆内存、元空间、GC次数、线程数、CPU负载、QPS、接口耗时;
④告警推送(AlertManager) :配置阈值触发告警,异常推送钉钉、企业微信、邮件,第一时间感知线上故障。
二、核心监控指标(面试必考)
①内存指标 :新生代、老年代、元空间内存占用、内存使用率、堆内存波动;
②GC指标 :YGC/FGC次数、GC停顿耗时、GC吞吐量、GC异常波动;
③线程指标 :总线程数、阻塞线程、死锁线程、空闲线程、线程峰值;
④系统指标 :服务器CPU使用率、磁盘使用率、负载、网络IO;
⑤业务指标 :接口QPS、响应耗时、异常率、请求报错数、熔断次数。
三、线上通用告警阈值(生产落地)
①堆内存使用率连续5分钟超过80%,触发内存告警;
②15分钟内出现3次以上FullGC,紧急告警;
③阻塞线程数量大于20条,判定线程积压;
④CPU使用率持续85%以上,触发CPU过载告警;
⑤接口异常率超过1%,推送业务异常告警。
四、完整部署流程
①项目引入actuator依赖,开启监控端点;
②配置Prometheus地址,定时采集Java服务指标;
③Grafana导入JVM专用模板,生成可视化大屏;
④配置告警规则+推送渠道,异常自动通知运维开发;
⑤结合链路追踪SkyWalking/Pinpoint,定位慢接口、慢SQL。
五、补充轻量化监控方案
临时简易监控:使用VisualVM、JConsole ;
线上轻量诊断:Arthas 实时排查;
企业生产标准:Actuator+Prometheus+Grafana 。
六、面试总结口诀
Actuator暴露指标、Prometheus存数据;
Grafana做大屏、告警推送防事故;
监控内存GC线程、CPU负载异常率;
线上标准监控栈、全部企业通用。
第七部分:大厂进阶压轴面试题
1.对象创建过程?对象内存布局?对象头详解?(超详细满分背诵版)
答:对象创建、内存布局是JVM高频基础面试题,重点考察新建对象底层执行流程、堆内存存储结构、对象头底层字节分布,全部通俗易懂、面试直接默写,完整版如下:
一、Java对象完整创建流程(六大步骤)
①类加载校验 :执行new关键字,先检查该类是否已完成加载、链接、初始化;未加载则触发类加载流程,校验字节码合法性;
②分配堆内存 :虚拟机在堆内存中开辟连续空间,存放对象;内存规整采用**指针碰撞**、内存碎片化采用**空闲列表**分配;
③初始化零值 :给对象所有成员变量赋予JVM默认零值,int=0、boolean=false、引用=null,保证对象初始化安全;
④设置对象头 :填充对象头MarkWord、类型指针、数组长度,记录哈希码、GC分代年龄、锁状态、类元信息;
⑤执行构造方法 :程序员自定义构造代码赋值,给成员变量设置业务初始值;
⑥返回对象引用 :栈中生成对象引用地址,指向堆内存真实对象,完成创建。
二、对象内存三大布局(HotSpot虚拟机)
Java对象在堆内存严格分为:对象头、实例数据、对齐填充 三部分;
1、对象头(核心重点)
分为 MarkWord + 类型指针,数组额外占用4字节存数组长度;
①MarkWord(运行时数据) :占用8字节,动态复用存储空间;
存储:对象哈希码、GC分代年龄、偏向锁标识、锁状态标记、线程ID;
特点:轻量级锁、偏向锁、重量级锁全部靠MarkWord标记切换;
②类型指针 :占用4字节,指向方法区Class类元数据,确定对象所属类;
③数组长度 :仅数组对象独有,占用4字节,记录数组长度。
2、实例数据
存放类中所有成员属性:基本数据类型、引用类型;
①基本类型:直接存数值,int4字节、long8字节;
②引用类型:存放堆内存地址指针,占用4字节;
③遵循内存排布规则:相同宽度数据集中排列,节省内存。
3、对齐填充(占位补齐)
HotSpot规定:对象内存大小必须是8字节的整数倍 ;
前面数据不足8字节倍数时,自动补空占位字节,无业务含义、单纯内存对齐;
作用:方便CPU快速读取内存数据,提升寻址效率。
三、对象内存大小简单举例
普通空对象:对象头12字节 + 对齐填充4字节 = 16字节;
四、面试高频追问
问:new对象一定在堆上吗?
答:不一定;JIT逃逸分析判定对象未逃逸,会栈上分配 ,不进入堆内存、无GC开销。
问:为什么需要内存对齐?
答:CPU读取内存按8字节块读取,对齐后减少读取次数,提升访问性能。
五、面试总结口诀
新建对象先加载、分配内存赋零值;
对象头部存标记、实例数据存属性;
对齐填充补空位、八倍对齐提性能;
逃逸分析栈分配、优化内存少GC。
2.对象在内存中的晋升流程?晋升规则?动态年龄判定?大对象机制?(超详细满分背诵版)
答:对象晋升是指新生代存活对象,经过多轮GC筛选后迁移至老年代的全过程;JVM依靠严格晋升规则筛选长期存活对象、淘汰短命对象,减轻新生代回收压力,是分代收集核心机制,完整流程+规则如下:
一、对象完整晋升流程(面试默写六步走)
①初始分配 :新建普通对象优先分配在Eden区,大对象直接分配进入老年代;
②首次GC筛选 :Eden区满触发MinorGC,死亡对象直接回收,存活对象移入From Survivor区;
③来回复制存活 :下一次MinorGC,将Eden+From存活对象复制到To Survivor,互换角色,反复筛选存活对象;
④年龄递增 :对象每熬过一次MinorGC,分代年龄+1,年龄存储在对象头MarkWord中;
⑤达标晋升 :对象年龄达到默认晋升阈值(默认15),直接晋升进入老年代;
⑥特殊晋升 :Survivor区存活对象过多、动态年龄判定达标、担保失败,提前晋升老年代。
二、三大晋升硬性规则(必考)
①年龄阈值晋升 :默认晋升年龄15岁,-XX:MaxTenuringThreshold配置;反复存活15次GC,强制晋升老年代;
②动态年龄判定(重点) :JVM不会死板等到15岁;当Survivor区相同年龄所有对象总大小,超过Survivor空间50%,直接将该年龄及以上对象全部晋升老年代,动态缩减晋升年龄;
③大对象直接晋升 :超过-XX:PretenureSizeThreshold阈值的超大对象,绕过新生代,直接分配到老年代,避免新生代复制搬运损耗;
④存活拥挤晋升 :MinorGC后存活对象过大,Survivor存放不下,直接通过分配担保机制晋升老年代。
三、对象分代年龄详解
①存储位置:对象头MarkWord,占用4bit;
②最大上限:二进制1111=15,所以最大晋升年龄不能超过15;
③作用:标记对象存活时长,筛选长期存活对象。
四、生产常见问题追问
问:为什么有些对象不到15岁就晋升?
答:触发动态年龄判定 ,存活对象过半,提前晋升保护Survivor内存规整,减少复制压力。
问:为什么大对象直接进老年代?
答:新生代复制算法搬运大对象成本极高,容易内存溢出,直接放入老年代。
五、面试总结口诀
新生对象放伊甸、存活进入幸存区;
来回复制涨年龄、十五上限进老年;
过半动态降年龄、超大对象直晋级。
3.为什么 Survivor 区要设置两个?如果只有一个会怎样?底层原理?(超详细满分背诵版)
答:新生代划分1块Eden区+2块Survivor区(8:1:1),专门适配标记复制算法 ;设置两块Survivor是JVM经过工程优化后的最优设计,目的是规避内存碎片、降低对象晋升频率、减少老年代压力,详细原理如下:
一、两块Survivor工作流程(面试必背)
①常态下:一块为From(使用区)、一块为To(空闲区),永远保证一块空闲;
②MinorGC触发:清空Eden区垃圾,将Eden+From存活对象,一次性复制到空白To区;
③角色互换:复制完成后,原To变为下一轮From,原From直接清空变成空白To;
④循环筛选:存活对象反复在两块Survivor之间来回复制,每复制一次年龄+1,筛选长期存活对象。
二、为什么不能只有一块Survivor?(核心痛点)
①产生大量内存碎片:单次GC后存活对象散乱分布,内存不规整,大量小空闲碎片无法利用;
②无法持续复制:只有一块Survivor,GC时没有空白中转区域,存活对象无处存放;
③频繁晋升老年代:内存碎片过多、剩余连续空间不足,存活对象被迫提前晋升老年代;
④加重老年代负担:大量短期对象涌入老年代,触发频繁MajorGC、FullGC,服务卡顿严重。
三、为什么不设置三块、四块?
①分区越多、内存切割越碎,大对象无法分配连续内存;
②多块分区增加内存维护成本、标记扫描开销变大;
③8:1:1两块分区结构最简单、复制成本最低、碎片最少,是工程最优解。
四、核心总结作用
①保证内存规整:依靠空闲互换机制,新生代永远无内存碎片;
②延迟对象晋升:反复筛选短期对象,尽量不让短命对象进入老年代;
③降低GC压力:减少老年代回收次数,减轻FullGC卡顿风险。
五、面试总结口诀
两个幸存一空闲、互换角色做中转;
单块碎片堆杂乱、短命对象乱晋升;
延迟进老年、减少GC卡顿、两块为最优。
4.锁消除、锁粗化原理?
答:均为 JIT 编译期优化;锁消除剔除无多线程竞争的无效同步锁;锁粗化合并连续多次加锁操作,减少锁竞争开销,提升并发执行效率。
5.ZGC 染色指针是什么?底层原理?存储结构?作用优势?(超详细满分背诵版)
答:染色指针(Colored Pointer)是ZGC专属核心底层技术 ,也是ZGC能够实现TB级大堆、毫秒级超低停顿的根本原因;区别于G1、CMS在堆中单独开辟空间标记GC状态,ZGC直接把GC标记信息写入对象指针,是JVM底层革命性优化,详细完整版如下:
一、染色指针核心定义
在64位操作系统下,对象引用指针占用64位二进制;ZGC对指针进行**高位比特位复用**,拿出高位闲置比特,存入GC标记状态、回收状态、重映射状态;这种携带GC状态标记的引用指针,统称为染色指针。
二、64位指针比特位拆分(面试必考)
①**高位4位:染色标记位**(核心),存储四种GC状态;
• 000:未标记(初始状态);
• 001:存活标记;
• 010:即将回收;
• 100:指针重映射;
②**中间44位:真实内存地址**,可寻址TB级内存;
③**低位16位:保留空位**,预留扩展使用。
三、三大核心作用
①实现并发标记 :无需扫描堆内存、无需修改对象头,直接通过指针高位判断对象存活状态,全程无需STW;
②并发内存整理 :ZGC移动对象后,无需一次性修正全部引用,依靠染色指针延迟更新地址,降低停顿;
③消除记忆集开销 :不需要G1那种昂贵的RememberedSet记忆集,依靠指针自带标记判断跨分区引用,节省大量内存。
四、底层配套技术(加分项)
①读屏障 :每次读取引用时检测染色位,自动修正指针地址,保证访问安全;
②转发表 :对象移动后旧地址映射新地址,配合染色指针延迟更新引用;
③无全局STW :几乎所有GC阶段全部并发执行,停顿时间恒定在1~10毫秒。
五、对比G1巨大优势
①G1依靠对象头、记忆集标记,内存开销大、扫描耗时;
②ZGC复用指针高位,无额外内存开销、无需扫描堆、标记速度极快;
③无论堆内存多大(10G~100G),停顿时间几乎不变。
六、面试总结口诀
染色指针存高位、复用比特省内存;
不扫堆、不卡顿、并发标记最轻盈;
配合读屏障、延迟改引用、毫秒停顿Z最强。
6.Tomcat 为什么破坏双亲委派?类加载层级?加载顺序?和JDBC破坏区别?(超详细满分背诵版)
答:Tomcat是典型打破双亲委派模型 的中间件,为解决web项目jar包冲突、类隔离、热部署问题,自定义专属类加载器,反向修改加载优先级;是面试必考、区分初级/中级工程师高频压轴题,完整版默写答案如下:
一、原生双亲委派存在的痛点
原生双亲委派:父加载器优先加载,全局一个类只能加载一次;
缺陷:无法实现同名不同版本类共存,多项目依赖冲突、版本打架,不适合web容器。
二、Tomcat四大自定义类加载器层级
①CommonClassLoader(公共加载器) :加载所有web项目通用依赖包,公共类共享;
②CatalinaClassLoader(容器加载器) :加载Tomcat自身核心底层源码,隔离容器与业务代码;
③SharedClassLoader(共享加载器) :加载部分项目共享jar,选择性共享依赖;
④WebappClassLoader(项目独有加载器) :每个web项目独立拥有,加载当前项目classes、lib依赖,互相隔离。
三、破坏后的加载执行顺序(重点必考)
打破自上而下委派,改为优先自己加载、后委托父加载器 :
①WebappClassLoader优先加载当前项目内部class、jar;
②项目找不到,再委托上层Shared、Common加载;
③最后才交给系统类加载器、启动类加载器;
严格实现:项目类优先、容器类隔离 。
四、为什么必须破坏双亲委派?三大核心目的
①实现项目类隔离(最核心) :一台Tomcat部署多个war包,不同项目依赖同名不同版本Jar;例如A项目Spring5、B项目Spring6,互不冲突、独立加载;原生双亲委派只能加载一份,必然版本冲突。
②容器与业务类隔离 :Tomcat自身依赖jar和业务jar分开加载,互不干扰,防止业务代码污染容器底层。
③支持热部署热加载 :单个项目修改class、更新jar,只需销毁当前WebappClassLoader,重新创建加载器加载类,不用重启整个Tomcat。
五、两次破坏双亲委派区别(大厂深挖追问)
①JDBC(第一次破坏) :正向委派不行,采用反向委托 ;启动类加载器委托应用类加载器加载驱动实现类;
②Tomcat(第二次破坏) :彻底颠倒加载顺序,子加载器优先加载 ,实现多项目隔离。
六、补充注意点
Tomcat针对自定义业务类、第三方jar 打破双亲委派;
对于JDK核心原生类(java.lang.*、java.util.*)依旧遵循双亲委派,保证系统安全,防止篡改核心类。
七、面试总结口诀
Tomcat打破双亲、子类优先加载;
多项目做隔离、jar版本不打架;
容器业务相分离、热部署不用重启;
核心JDK类不变、安全优先不篡改。
第八部分:面试手写图形附录(默写必画·极简ASCII图)
说明:本章节全部为考试、面试手写简图,简单易画、不拉跨、通俗易懂,背诵配套图形,面试官好感拉满。
图1:JVM整体四大结构总图(开篇必考)
┌─────────────────────────────┐
│ 类加载子系统(Load) │
└───────────┬───────────────────┘
↓
┌─────────────────────────────┐
│ 运行时数据区(内存模型) │
└───────────┬───────────────────┘
↓
┌─────────────────────────────┐
│ 执行引擎(解释器+JIT+GC回收器) │
└───────────┬───────────────────┘
↓
┌─────────────────────────────┐
│ JNI本地方法接口(C/C++) │
└─────────────────────────────┘
图2:JVM运行时数据区内存划分图(必考)
JVM内存区域
├─线程私有(无GC、随线程销毁)
│ ├─程序计数器(唯一无OOM)
│ ├─Java虚拟机栈(栈帧、局部变量)
│ └─本地方法栈(Native方法)
│
└─线程共享(有GC、易OOM)
├─Java堆(新生代+老年代,对象实例)
├─方法区(元空间:类元信息、静态变量)
├─运行时常量池
└─直接内存(堆外内存、NIO专用)
图3:堆内存分代结构图(8:1:1)
Java堆
├─新生代(1/3堆内存)
│ ├─Eden区(80%) ←新生对象优先分配
│ ├─From Survivor(10%)
│ └─To Survivor(10%) ←永远空闲
│
└─老年代(2/3堆内存)
├─长期存活对象
├─大对象直接存放
└─GC频率低、卡顿高
图4:对象内存布局结构图(HotSpot)
Java对象内存结构
├─对象头(12字节)
│ ├─MarkWord(8B):哈希码、分代年龄、锁标记
│ └─类型指针(4B):指向类元数据
├─实例数据:成员变量、基本类型、引用指针
└─对齐填充:补位至8的整数倍(无业务含义)
图5:对象晋升流程图(默写最简版)
new对象 → Eden区
↓(MinorGC存活)
From Survivor ←→ To Survivor
↓(年龄达标/动态判定)
老年代常驻
图6:双亲委派模型层级图
加载顺序:自下而上委托,自上而下加载
┌─────────────────────┐
│ 启动类加载器(Bootstrap)│ 加载JDK核心类
└───────────┬─────────┘
↑
┌─────────────────────┐
│ 平台类加载器(Platform)│ 系统扩展类
└───────────┬─────────┘
↑
┌─────────────────────┐
│ 应用类加载器(App) │ 项目自定义类
└───────────┬─────────┘
↑
┌─────────────────────┐
│ 自定义类加载器 │ 加密/热部署
└─────────────────────┘
图7:Tomcat类加载器破坏双亲结构图
Tomcat加载顺序:子类优先(打破双亲)
WebappClassLoader(项目优先加载)
↓(找不到再向上)
SharedClassLoader → CommonClassLoader → 系统加载器
特点:项目jar优先、隔离多版本、互不冲突
图8:ZGC染色指针64位结构图
64位指针二进制划分
├─高位4位:染色标记位(存活/回收/重映射)
├─中间44位:真实物理内存地址
└─低位16位:系统保留空位
优势:不占堆内存、无需记忆集、并发标记
图9:线上JVM监控架构图(Actuator+Prometheus+Grafana)
Java服务 → Actuator暴露metrics接口
↓
Prometheus定时采集、时序存储
↓
Grafana可视化大屏 + AlertManager告警推送
图10:GC引用链可达性分析图
GC Roots(栈/静态/常量/Native)
↓遍历
├─存活对象(保留)
└─不可达对象(判定垃圾、回收)
特点:彻底解决循环引用无法回收问题
图11:对象头MarkWord锁状态流转图(面试必考锁升级)
无锁状态 → 偏向锁(单线程)
↓竞争
轻量级锁(自旋无阻塞)
↓激烈竞争
重量级锁(阻塞排队)
特点:锁单向升级、不可降级、偏向→轻量→重量
图12:CMS垃圾回收执行流程图
1.初始标记(短暂STW) → 标记GC Roots直达对象
↓
2.并发标记(用户线程并行) → 遍历全部引用链
↓
3.重新标记(短暂STW) → 修正并发变动引用
↓
4.并发清除(并行执行) → 清空垃圾、产生碎片
特点:两次STW、并发执行、老年代标记清除
图13:G1 Region分区内存结构图
整个堆内存 = 2048个大小一致Region分区
├─Eden Region:新生对象存放
├─Survivor Region:存活中转
├─Old Region:长期存活对象
└─Humongous Region:超大对象专用
特点:分区回收、垃圾优先、内存规整
图14:ZGC超低延迟执行流程图
染色指针标记 → 并发标记(无STW)
↓
并发重映射 + 读屏障修正指针
↓
并发压缩整理内存(移动对象)
↓
几乎全程并发、停顿1~10ms
特点:TB大堆、无记忆集、极低卡顿
图15:Java线程五大状态流转图
新建(NEW) → 启动start()
↓
运行(RUNNABLE) ⇄ 阻塞(BLOCKED)
↓sleep/wait
等待(WAITING)/超时等待(TIMED_WAITING)
↓
销毁(TERMINATED)
特点:阻塞不占用CPU、等待主动释放锁
图16:DCL双重校验锁单例流程图
外部调用获取单例
↓
第一层判断(是否为null)→有直接返回
↓无实例
加锁synchronized
↓
第二层判断(防止重复创建)
↓
初始化对象(volatile禁止重排)
返回唯一单例
图17:四种引用强弱层级结构图
引用强度从高到低排序
强引用 ● 永不回收(默认)
↓
软引用 ● 内存不足才回收(缓存)
↓
弱引用 ● 只要GC就回收(防泄漏)
↓
虚引用 ● 仅监控回收(堆外内存)
图18:JVM线上故障排查流程图
线上服务异常 → top定位高负载进程
↓
jstat观测GC内存走势
↓
jstack排查线程卡死/死循环
↓
jmap导出dump快照
↓
MAT分析内存泄漏、修复代码
图19:虚拟机栈栈帧内部结构图(必考)
单个栈帧结构(方法私有)
├─局部变量表:基本类型+对象引用
├─操作数栈:运算中间缓存数据
├─动态链接:符号引用→直接引用
├─方法返回地址:执行完毕跳转位置
└─附加信息:异常表、调试信息
特点:栈帧后进先出、方法结束自动销毁
图20:MarkWord锁升级位图(通俗易懂版)
64位MarkWord简易分布
【无锁】 哈希码+分代年龄+001
【偏向锁】线程ID+时间戳+年龄+005
【轻量级锁】 锁指针 + 00
【重量级锁】 互斥锁指针 + 10
【GC标记】 空 + 11
规则:单向升级、不可逆、不可降级
图21:JMM主内存+工作内存交互图
CPU缓存
↑↓
┌──────────────┐
│ 工作内存(线程私有) │
│ 变量副本、缓存数据 │
└───────┬──────┘
↑↓
┌──────────────┐
│ 主内存(共享内存) │
│ 成员变量、静态变量 │
└──────────────┘
交互:读拷贝、写回刷,volatile强制刷新
图22:GC三色标记算法流程图(并发标记核心)
三色标记(解决并发漏标)
白色:未扫描,初始全部白色
灰色:正在扫描,有引用未遍历完
黑色:扫描完毕,确定存活
规则:
黑不能引用白、灰色遍历全部、白色最终回收
补充:CMS/G1/ZGC底层全部依赖三色标记
图23:类加载完整五阶段流程图
.class字节码文件
↓
加载→生成Class对象、载入元数据
↓
验证→四层校验、过滤恶意字节码
↓
准备→静态变量赋默认零值
↓
解析→符号引用转直接内存引用
↓
初始化→静态代码块、自定义赋值