JVM高频面试总结(背诵完整版)

第一部分:架构与内存篇(必考)

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对象、载入元数据

验证→四层校验、过滤恶意字节码

准备→静态变量赋默认零值

解析→符号引用转直接内存引用

初始化→静态代码块、自定义赋值

相关推荐
ChoSeitaku6 小时前
11.异常_throws_try...catch_BigInteger_BigDecimal_Date_Calendar_LocalDate_Integer
java
胡志辉的博客6 小时前
完全开源、本地 SQLite 管理一切:我写了一个桌面邮件客户端 OneMail
java·sqlite·开源
沪漂阿龙6 小时前
Java JVM 面试题详解:JVM运行原理、内存模型、堆栈方法区、GC垃圾回收、JIT编译、类加载机制与线上调优全攻略
java·开发语言·jvm
小碗羊肉6 小时前
Maven高级
java·开发语言·maven
不知名的老吴6 小时前
C++ 中函数对象的形式概述
开发语言·c++
星秀日6 小时前
Spring Boot + Sa-Token 实时聊天系统:用户注册流程源码深度剖析
java·人工智能·spring·状态模式
Shan12056 小时前
C++中函数对象之重载 operator()
开发语言·c++·算法
HelloWorld1024!6 小时前
c++核心之万字详解 * 和 & 所有用法(指针、引用、取地址、解引用、常量修饰)
开发语言
Legendary_0086 小时前
解析 PD Sink 与 LDR6500U:Type-C 取电的核心密码
c语言·开发语言