学习jvm 这一篇就够了
一、JVM 概述
1.1 什么是 JVM

JVM(Java Virtual Machine,Java 虚拟机)是运行 Java 字节码的虚拟计算机,它是 Java 语言实现 “一次编写,到处运行”(Write Once, Run Anywhere)核心特性的关键组件。Java 源代码经编译器(javac)编译成字节码文件(.class)后,JVM 负责将字节码解释或编译为本地机器指令,最终在操作系统上执行。
JVM 屏蔽了不同操作系统的底层差异,使得同一份字节码能在 Windows、Linux、macOS 等不同平台上运行,无需针对特定平台修改代码。
1.2 JVM 的核心作用
- 字节码执行:通过解释器(Interpreter)逐行解释字节码,或通过即时编译器(JIT,Just-In-Time Compiler)将热点代码(频繁执行的代码)编译为本地机器码,提升执行效率。
- 内存管理:自动负责对象的内存分配(如在堆中创建对象)和垃圾回收(Garbage Collection,GC),避免手动管理内存导致的内存泄漏、野指针等问题。
- 安全检查:在类加载过程中通过 “类加载器双亲委派模型” 防止恶意类篡改,同时对字节码进行验证(如语法检查、类型安全检查),保障程序运行安全。
- 线程管理:映射 Java 线程到操作系统原生线程,管理线程的生命周期(创建、运行、阻塞、终止),并通过内置的同步机制(如 synchronized)实现线程安全。
二、JVM 架构组成
JVM 架构主要分为五大核心模块,各模块协同工作完成字节码的执行和资源管理,具体结构如下:

| 模块 | 核心功能 |
|---|---|
| 类加载子系统 | 负责加载 .class 文件到内存,完成 “加载、验证、准备、解析、初始化”5 个阶段 |
| 运行时数据区 | 存储程序运行过程中的所有数据(如对象、变量、线程栈等),是 JVM 内存核心 |
| 执行引擎 | 执行字节码,包含解释器、JIT 编译器、垃圾回收器(GC) |
| 本地方法接口(JNI) | 衔接 Java 代码与本地 C/C++ 代码,允许调用操作系统原生方法(如文件操作) |
| 本地方法库 | 存放 JNI 调用的本地方法实现,是操作系统底层功能的 Java 封装 |
三、类加载子系统
类加载子系统负责将磁盘上的 .class 文件加载到 JVM 运行时数据区,整个过程分为加载、验证、准备、解析、初始化5 个阶段,只有经过这 5 个阶段,类才能被使用。
3.1 类加载的 5 个阶段
-
加载(Loading)
- 核心任务:通过类的全限定名(如
java.lang.String)找到对应的 .class 文件,读取文件字节流,将其转换为运行时数据区中的 “类元数据”(存储在方法区),并在堆中创建一个java.lang.Class对象,作为访问类元数据的入口。 - 示例:通过
ClassLoader.loadClass("com.example.User")触发加载。
- 核心任务:通过类的全限定名(如
-
验证(Verification)
- 核心任务:确保 .class 文件的字节流符合 JVM 规范,防止恶意字节码(如篡改指令、破坏类型安全)导致 JVM 崩溃。
- 主要验证内容:文件格式验证(如魔数是否为 0xCAFEBABE)、元数据验证(如类是否有父类、是否实现接口)、字节码验证(如指令合法性、类型转换安全)、符号引用验证(如引用的类 / 方法是否存在)。
-
准备(Preparation)
- 核心任务:为类的静态变量(static 修饰)分配内存,并设置默认初始值(而非代码中定义的初始值)。
- 示例:
public static int num = 10;在准备阶段,num 会被分配内存并设置为默认值 0,而非 10;10 的赋值会在 “初始化” 阶段完成。 - 注意:如果静态变量被
final修饰(如public static final int NUM = 10;),则在准备阶段会直接设置为 10(编译时已确定值,存储在常量池中)。
-
解析(Resolution)
- 核心任务:将类元数据中的 “符号引用”(如
com.example.User是字符串形式的引用)转换为 “直接引用”(如内存地址),建立类、方法、字段与内存地址的关联。 - 触发时机:部分解析可能在 “初始化” 后延迟执行(如动态绑定的方法调用),而非严格在准备阶段后执行。
- 核心任务:将类元数据中的 “符号引用”(如
-
初始化(Initialization)
- 核心任务:执行类的静态代码块(
static {})和静态变量的赋值语句,完成类的初始化。这是类加载的最后阶段,只有初始化完成后,类才能被实例化(创建对象)或调用静态方法 / 变量。 - 初始化触发条件(主动使用场景):
- 创建类的实例(如
new User()); - 调用类的静态方法(如
User.staticMethod()); - 访问类的静态变量(如
int n = User.num); - 反射调用(如
Class.forName("com.example.User")); - 初始化子类时,父类会先初始化(子类依赖父类)。
- 创建类的实例(如
- 核心任务:执行类的静态代码块(
3.2 类加载器与双亲委派模型
类加载器(ClassLoader)是实现 “加载” 阶段的核心组件,JVM 内置了 3 种类加载器,且遵循双亲委派模型加载类,确保类加载的安全性和唯一性。
3.2.1 内置类加载器
-
启动类加载器(Bootstrap ClassLoader)
- 负责加载 JVM 核心类库(如
java.lang、java.util包下的类),这些类存放在 JRE 的lib目录(如rt.jar)。 - 特点:由 C/C++ 实现,不属于 Java 类(无法通过
getClass()获取其对象),返回null。
- 负责加载 JVM 核心类库(如
-
扩展类加载器(Extension ClassLoader)
- 负责加载 JRE 扩展目录(
lib/ext)下的类库,如javax包下的部分类。 - 实现类:
sun.misc.Launcher$ExtClassLoader。
- 负责加载 JRE 扩展目录(
-
应用程序类加载器(Application ClassLoader)
- 负责加载用户编写的类(即项目
classpath下的 .class 文件),是默认的类加载器。 - 实现类:
sun.misc.Launcher$AppClassLoader,可通过ClassLoader.getSystemClassLoader()获取。
- 负责加载用户编写的类(即项目
-
自定义类加载器(Custom ClassLoader)
- 自定义类加载器是开发者通过继承
java.lang.ClassLoader类实现的类加载器,用于满足特定场景下的类加载需求。其核心作用是突破 JVM 内置类加载器的固定加载路径限制,灵活加载来自自定义来源的类(打破双亲委派机制)。 - 特点:自定义类加载器通过继承
ClassLoader实现,可从自定义来源(如加密文件、网络)加载类,遵循双亲委派模型,兼具灵活性与安全性,常用于类隔离、加密保护等场景 - 自定义类加载器deom
- 自定义类加载器是开发者通过继承
public class CustomClassLoader extends ClassLoader {
// 构造方法:指定父加载器(默认父加载器为应用程序类加载器)
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
// 重写findClass方法:实现自定义加载逻辑
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 1. 从自定义来源读取类字节码(如加密文件、网络)
byte[] classData = loadClassBytes(name);
if (classData == null) {
throw new ClassNotFoundException("类未找到:" + name);
}
// 2. 将字节码转换为Class对象(由JVM实现,不可重写)
return defineClass(name, classData, 0, classData.length);
} catch (Exception e) {
throw new ClassNotFoundException("加载类失败:" + name, e);
}
}
// 自定义方法:从特定来源加载类字节码
private byte[] loadClassBytes(String className) throws IOException {
// 示例:从自定义目录加载(实际可扩展为网络、加密文件等)
String path = className.replace('.', '/') + ".class";
// 读取字节码(若为加密文件,此处需添加解密逻辑)
try (InputStream is = new FileInputStream("/custom/classes/" + path)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
}
3.2.2 双亲委派模型
- 核心规则:当一个类加载器需要加载类时,会先委托给父加载器加载,只有父加载器无法加载(找不到对应的 .class 文件)时,才由当前加载器自己加载。
- 委派顺序:应用程序类加载器 → 扩展类加载器 → 启动类加载器(顶层)。
- 作用:
- 防止类重复加载:如
java.lang.String只能由启动类加载器加载,避免用户自定义同名类篡改核心类。 - 保障安全:禁止用户编写恶意类(如
java.lang.String)替换 JVM 核心类,因为父加载器(启动类加载器)已加载核心类,子加载器不会重复加载。
- 防止类重复加载:如
四、运行时数据区
运行时数据区是 JVM 存储程序运行数据的区域,根据 JVM 规范,分为程序计数器、虚拟机栈、本地方法栈、堆、方法区5 个部分,其中前 3 个区域为 “线程私有”(每个线程对应一份),后 2 个区域为 “线程共享”(所有线程共用)。

4.1 线程私有区域(线程创建时初始化,线程销毁时回收)
4.1.1 程序计数器(Program Counter Register)
- 作用:记录当前线程执行的字节码指令的行号(即下一条要执行的指令地址),确保线程切换后能恢复到正确的执行位置。
- 特点:
- 是 JVM 中唯一没有
OutOfMemoryError(OOM)异常的区域,因为它只存储指令地址,内存大小固定。 - 若线程执行的是 Java 方法,计数器存储字节码指令地址;若执行的是本地方法(JNI 方法),计数器值为
undefined。
- 是 JVM 中唯一没有
4.1.2 虚拟机栈(VM Stack)
- 作用:存储线程执行 Java 方法时的 “栈帧”(Stack Frame),每个方法调用对应一个栈帧的入栈,方法执行完成后栈帧出栈。
- 栈帧组成:
- 局部变量表:存储方法的局部变量(如参数、临时变量),内存大小在编译时确定。
- 操作数栈:作为方法执行的临时数据栈(如计算
a + b时,先将a、b压栈,再执行加法指令)。 - 动态链接:指向方法区中当前方法的符号引用,用于将符号引用转换为直接引用。
- 方法返回地址:记录方法执行完成后,返回给调用者的指令地址。
- 异常:
StackOverflowError:线程请求的栈深度超过虚拟机栈的最大深度(如递归调用无终止条件)。OutOfMemoryError:虚拟机栈可动态扩展时,扩展内存失败(如创建大量线程导致栈内存耗尽)。
4.1.3 本地方法栈(Native Method Stack)
- 作用:与虚拟机栈类似,但专门用于存储线程执行本地方法(JNI 方法,如
System.currentTimeMillis())时的栈帧。 - 异常:与虚拟机栈一致,可能抛出
StackOverflowError或OutOfMemoryError。
4.2 线程共享区域(JVM 启动时初始化,JVM 关闭时回收)
4.2.1 堆(Heap)

- 作用:存储 Java 程序中所有对象实例(如
new User()创建的对象)和数组,是 JVM 内存中最大的区域,也是垃圾回收(GC)的主要区域。 - 内存划分(逻辑划分,方便 GC):
- 年轻代(Young Generation):存储新创建的对象,分为 Eden 区、From Survivor 区、To Survivor 区(比例通常为 8:1:1)。
- Eden 区:新对象优先在 Eden 区分配内存,当 Eden 区满时触发 “Minor GC”(年轻代 GC),存活的对象进入 Survivor 区。
- Survivor 区:From 和 To 区交替使用,每次 Minor GC 后,存活对象在两个 Survivor 区之间转移,当对象年龄达到阈值(默认 15)时,进入老年代。
- 老年代(Old Generation):存储存活时间长的对象(如缓存对象、单例对象),当老年代满时触发 “Major GC”(老年代 GC,也叫 Full GC),Full GC 执行效率远低于 Minor GC。
- 元空间(Metaspace,JDK 8+):替代 JDK 7 及之前的 “永久代”,存储类元数据(如类结构、方法信息、常量池),元空间使用本地内存(而非 JVM 堆内存),默认无大小限制(可通过参数配置)。
- 年轻代(Young Generation):存储新创建的对象,分为 Eden 区、From Survivor 区、To Survivor 区(比例通常为 8:1:1)。
- 异常:
OutOfMemoryError: Java heap space(堆内存不足,如创建大量对象且无法被 GC 回收)。
4.2.2 方法区(Method Area)
- 作用:存储线程共享的类元数据(类结构、方法信息、字段信息)、常量池(如字符串常量、数字常量)、静态变量、即时编译器编译后的代码(JIT 缓存)。
- 演变:
- JDK 7 及之前:方法区实现为 “永久代”(PermGen),属于堆的一部分,有固定大小限制,容易出现
OutOfMemoryError: PermGen space。 - JDK 8 及之后:方法区实现为 “元空间”(Metaspace),使用本地内存,默认无大小限制(可通过
MetaspaceSize、MaxMetaspaceSize配置),避免了永久代的内存溢出问题。
- JDK 7 及之前:方法区实现为 “永久代”(PermGen),属于堆的一部分,有固定大小限制,容易出现
- 常量池:
- 运行时常量池:是方法区的一部分,存储类编译后的常量(如
String s = "abc"中的"abc"会存入常量池),支持动态常量(如String s = new String("abc")会在堆中创建对象,同时常量池若没有"abc"则会添加)。
- 运行时常量池:是方法区的一部分,存储类编译后的常量(如
五、垃圾回收(GC)
垃圾回收(Garbage Collection)是 JVM 自动回收堆中 “无用对象”(不再被引用的对象)内存的过程,无需手动释放内存,是 JVM 内存管理的核心机制。
5.1 垃圾判定算法(如何判断对象是 “垃圾”)
5.1.1 引用计数法(Reference Counting)
- 原理:为每个对象添加一个 “引用计数器”,当对象被引用时计数器加 1,引用失效时计数器减 1;当计数器为 0 时,判定为垃圾对象。
- 优点:实现简单,判定效率高。
- 缺点:无法解决 “循环引用” 问题(如 A 引用 B,B 引用 A,两者均无其他引用,但计数器均为 1,无法被回收),因此 JVM 未采用此算法。
5.1.2 可达性分析算法(Reachability Analysis)
- 原理:以 “GC Roots”(根对象)为起点,遍历对象引用链(如从 GC Roots 出发能找到的对象为 “可达对象”,找不到的为 “不可达对象”,不可达对象即为垃圾对象)。
- GC Roots 包括:
- 虚拟机栈中局部变量表引用的对象(如当前执行方法的参数、临时变量);
- 本地方法栈中引用的对象(JNI 方法引用的对象);
- 方法区中静态变量引用的对象(如
static User user = new User()); - 方法区中常量引用的对象(如
final User user = new User()); - JVM 内部引用(如 Class 对象、异常对象
Throwable)。
- 优点:解决了循环引用问题,是 JVM 采用的核心垃圾判定算法。
5.2 垃圾回收算法(如何回收垃圾对象)
5.2.1 标记 - 清除算法(Mark-Sweep)

- 步骤:
- 标记:通过可达性分析,标记所有可达对象(非垃圾),未标记的为垃圾对象。
- 清除:遍历堆内存,回收所有未标记的垃圾对象,释放内存。
- 优点:实现简单,无需移动对象。
- 缺点:
- 产生内存碎片(回收后的内存不连续,后续大对象可能无法分配连续内存);
- 标记和清除过程效率低(需遍历整个堆)。
5.2.2 复制算法(Copying)

- 步骤:
- 将堆内存划分为两个大小相等的区域(如 From Survivor 和 To Survivor),每次只使用其中一个区域(From)。
- 标记 From 区中的可达对象,将其复制到 To 区,且按顺序排列(避免内存碎片)。
- 清空 From 区,交换 From 和 To 的角色,下次 GC 重复上述过程。
- 优点:无内存碎片,回收效率高(只需复制可达对象)。
- 缺点:内存利用率低(仅使用一半内存),适合对象存活率低的区域(如年轻代的 Eden 区和 Survivor 区,Minor GC 采用此算法)。
5.2.3 标记 - 整理(压缩)算法(Mark-Compact)

- 步骤:
- 标记:与标记 - 清除算法一致,标记所有可达对象。
- 整理:将所有可达对象向堆内存的一端移动,按顺序排列,然后清空移动后另一端的垃圾对象内存。
- 优点:无内存碎片,内存利用率高(无需划分两个区域)。
- 缺点:增加了对象移动的开销,适合对象存活率高的区域(如老年代,Major GC 采用此算法)。
5.2.4 分代收集算法(Generational Collection)

- 原理:根据对象存活时间的不同,将堆分为年轻代和老年代,针对不同代的特点采用不同的 GC 算法:
- 年轻代(对象存活率低):采用复制算法(Minor GC),效率高。
- 老年代(对象存活率高):采用标记 - 清除算法或标记 - 整理算法(Major GC/Full GC),平衡内存碎片和效率。
- 特点:结合了不同算法的优点,是当前所有 JVM 垃圾收集器的核心思想。
5.3 常见垃圾收集器
垃圾收集器是 GC 算法的具体实现,不同收集器针对 “吞吐量”“延迟”“内存占用” 等指标做了不同优化,适用于不同业务场景(如高并发服务侧重低延迟,后台任务侧重高吞吐量)。以下是 JDK 中主流的垃圾收集器:
| 收集器类型 | 适用代际 | 核心特点 | 适用场景 |
|---|---|---|---|
| Serial 收集器 | 年轻代 | - 单线程收集(GC 时暂停所有用户线程,即 STW,Stop The World)- 实现简单、内存占用少 | 客户端应用(如桌面程序)、单核 CPU 环境 |
| ParNew 收集器 | 年轻代 | - Serial 的多线程版本(GC 时使用多线程回收,减少 STW 时间)- 可与 CMS 收集器配合使用 | 服务端应用(如 Tomcat),多核 CPU 环境 |
| Parallel Scavenge 收集器 | 年轻代 | - 多线程收集,侧重 “吞吐量优先”(吞吐量 = 用户线程执行时间 / 总时间)- 可自动调节 GC 参数(如 -XX:+UseAdaptiveSizePolicy) | 后台计算任务(如数据批处理),追求高吞吐量 |
| Serial Old 收集器 | 老年代 | - Serial 的老年代版本,单线程收集,基于 “标记 - 整理算法”- STW 时间较长 | 客户端应用、与 Parallel Scavenge 配合使用 |
| Parallel Old 收集器 | 老年代 | - Parallel Scavenge 的老年代版本,多线程收集,基于 “标记 - 整理算法”- 侧重吞吐量优先 | 与 Parallel Scavenge 搭配,适用于高吞吐量场景 |
| CMS 收集器(Concurrent Mark Sweep) | 老年代 | - 基于 “标记 - 清除算法”,追求 “低延迟”(GC 线程与用户线程并发执行,减少 STW 时间)- 执行流程:初始标记 → 并发标记 → 重新标记 → 并发清除 | 高并发服务(如电商支付、金融交易),侧重低延迟 |
| G1 收集器(Garbage-First) | 全代(年轻代 + 老年代) | - 基于 “区域划分”(将堆分为多个大小相等的 Region),兼顾吞吐量与低延迟- 优先回收垃圾占比高的 Region(Garbage-First 含义)- 基于 “标记 - 整理算法”,无内存碎片 | JDK 9+ 默认收集器,适用于大内存(如 8G+)服务 |
| ZGC 收集器 | 全代 | - 低延迟(STW 时间控制在 10ms 以内),支持 TB 级内存- 基于 “着色指针” 和 “读屏障” 技术,并发执行大部分 GC 步骤 | 超大内存、低延迟要求极高的场景(如大型分布式系统) |
| Shenandoah 收集器 | 全代 | - 与 ZGC 目标类似,低延迟、支持大内存- 基于 “连接矩阵” 和 “写屏障” 技术,不依赖特定 JVM 实现 | OpenJDK 专属,适用于低延迟、大内存场景 |
5.4 GC 相关参数配置
实际开发中,需根据业务场景配置 JVM GC 参数,优化性能。以下是常用的核心参数:
| 参数格式 | 作用说明 | 示例 |
|---|---|---|
-Xms<size> |
设置堆初始内存大小(与 -XX:InitialHeapSize 等价) |
-Xms2g(初始堆内存 2GB) |
-Xmx<size> |
设置堆最大内存大小(与 -XX:MaxHeapSize 等价) |
-Xmx4g(最大堆内存 4GB) |
-Xmn<size> |
设置年轻代内存大小(Eden + Survivor),剩余为老年代 | -Xmn1g(年轻代 1GB,老年代 3GB,若 -Xmx4g) |
-XX:SurvivorRatio=<ratio> |
设置 Eden 区与单个 Survivor 区的比例(默认 8,即 Eden:From:To = 8:1:1) | -XX:SurvivorRatio=4(Eden:From:To = 4:1:1) |
-XX:MaxTenuringThreshold=<age> |
设置对象进入老年代的最大年龄(默认 15,即对象在 Survivor 区存活 15 次 Minor GC 后进入老年代) | -XX:MaxTenuringThreshold=10 |
-XX:+UseG1GC |
指定使用 G1 收集器(JDK 9+ 默认,JDK 8 需手动指定) | -XX:+UseG1GC |
-XX:+UseConcMarkSweepGC |
指定使用 CMS 收集器(JDK 9 后已废弃,推荐 G1) | -XX:+UseConcMarkSweepGC |
-XX:MetaspaceSize=<size> |
设置元空间初始大小(JDK 8+,替代永久代) | -XX:MetaspaceSize=128m |
-XX:MaxMetaspaceSize=<size> |
设置元空间最大大小(默认无限制,建议配置避免元空间溢出) | -XX:MaxMetaspaceSize=256m |
-XX:+PrintGCDetails |
打印 GC 详细日志(包含回收区域、时间、内存变化等) | -XX:+PrintGCDetails |
-XX:+HeapDumpOnOutOfMemoryError |
当发生 OOM 时,自动生成堆转储文件(.hprof),用于分析内存泄漏 | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./oom.hprof |
六、JVM 内存溢出与排查
6.1 常见内存溢出(OOM)类型
JVM 运行时可能因内存分配不足或资源泄漏导致 OOM,不同区域的 OOM 表现不同:
-
堆内存溢出(Java heap space)
- 原因:创建大量对象且无法被 GC 回收(如内存泄漏),或堆内存配置过小。
- 示例:循环创建对象并放入集合,且集合未被释放:
java
运行
List<Object> list = new ArrayList<>(); while (true) { list.add(new Object()); // 对象持续被 list 引用,无法 GC,最终 OOM } - 排查工具:JVisualVM、MAT(Memory Analyzer Tool),分析堆转储文件(.hprof)定位泄漏对象。
-
虚拟机栈 / 本地方法栈溢出(StackOverflowError)
- 原因:线程栈深度超过 JVM 限制(如递归调用无终止条件),或栈内存配置过小(
-Xss参数)。 - 示例:无限递归调用:
java
运行
public void infiniteRecursion() { infiniteRecursion(); // 每次调用都会创建新栈帧,栈深度不断增加,最终 StackOverflowError } - 排查:检查递归逻辑是否有终止条件,或增大栈内存(如
-Xss256k)。
- 原因:线程栈深度超过 JVM 限制(如递归调用无终止条件),或栈内存配置过小(
-
元空间溢出(Metaspace OOM)
- 原因:加载的类过多(如动态生成大量类、依赖包过大),且元空间最大大小未限制。
- 场景:使用 Spring、MyBatis 等框架时,动态代理生成大量代理类;或频繁使用反射创建类。
- 排查:配置
-XX:MaxMetaspaceSize限制元空间大小,或分析类加载情况(如通过jmap -clstats <pid>查看类加载统计)。
-
直接内存溢出(Direct buffer memory)
- 原因:使用 NIO 的
DirectByteBuffer分配直接内存(不受堆内存限制,受物理内存限制),且未及时释放。 - 示例:频繁分配直接内存且不回收:
java
运行
while (true) { ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 分配 1MB 直接内存,不释放 } - 排查:通过
-XX:MaxDirectMemorySize限制直接内存大小,或检查DirectByteBuffer的使用是否有泄漏。
- 原因:使用 NIO 的
6.2 JVM 故障排查工具
日常开发中,需借助工具定位 JVM 性能问题(如 OOM、GC 频繁、CPU 过高),常用工具如下:
| 工具名称 | 核心功能 | 使用场景 |
|---|---|---|
| jps(Java Process Status) | 列出当前运行的 Java 进程(PID、主类名) | 快速查找目标 Java 进程的 PID |
| jstat(Java Statistics Monitoring) | 实时监控 JVM 统计信息(如 GC 次数、堆内存使用、类加载数量) | 查看 GC 频率、堆内存变化,判断是否有 GC 频繁问题 |
| jmap(Java Memory Map) | 生成堆转储文件(.hprof)、查看堆内存使用情况、查看类加载统计 | 分析内存泄漏、查看对象分布(如 jmap -dump:format=b,file=heap.hprof <pid>) |
| jstack(Java Stack Trace) | 生成线程栈快照(查看线程状态、调用栈) | 定位线程死锁、CPU 过高的线程(如找到阻塞线程的调用栈) |
| jhat(Java Heap Analysis Tool) | 分析 jmap 生成的堆转储文件,提供 Web 界面查看对象引用关系 | 简单分析堆内存泄漏(推荐使用更强大的 MAT 工具) |
| MAT(Memory Analyzer Tool) | 专业堆内存分析工具,支持查找内存泄漏、分析对象引用链、计算对象大小 | 深度分析 OOM 问题,定位泄漏源头 |
| JVisualVM | 集成 jps、jstat、jmap、jstack 功能,提供图形化界面,支持监控、分析、 profiling | 日常 JVM 监控与简单故障排查(JDK 自带,位于 JDK/bin 目录) |
七、JVM 性能优化核心思路
JVM 优化的目标是减少 GC 频率、缩短 STW 时间、避免 OOM、提升应用响应速度,核心思路围绕 “内存配置”“GC 选择”“代码优化” 三个维度展开:
7.1 内存配置优化
-
堆内存配置(-Xms 与 -Xmx 一致)
- 建议将
-Xms(初始堆)与-Xmx(最大堆)设置为相同值,避免 JVM 频繁调整堆内存大小(减少性能开销)。 - 堆内存大小建议:根据物理内存确定,如物理内存为 8GB 时,堆内存可设置为
-Xms4g -Xmx4g(避免占用过多物理内存导致系统资源不足)。
- 建议将
-
年轻代与老年代比例
- 年轻代(-Xmn)建议占堆内存的 1/3 ~ 1/2:年轻代过小会导致 Minor GC 频繁,过大则会减少老年代内存,可能导致 Major GC 频繁。
- 示例:堆内存为 4GB 时,年轻代可设置为
-Xmn1.5g(老年代 2.5GB)。
-
元空间配置
- 必须配置
-XX:MaxMetaspaceSize(如-XX:MaxMetaspaceSize=256m),避免元空间无限膨胀导致 OOM。 - 初始元空间(-XX:MetaspaceSize)建议设置为默认值或根据依赖包大小调整(如 128m)。
- 必须配置
7.2 GC 收集器选择
-
低延迟场景(如电商、金融)
- 优先选择 G1 收集器(JDK 9+ 默认),或 ZGC/Shenandoah(超大内存场景),通过
-XX:+UseG1GC启用。 - 配置 G1 相关参数:
-XX:MaxGCPauseMillis=200(目标 STW 时间 200ms)、-XX:InitiatingHeapOccupancyPercent=45(堆内存占用 45% 时触发 G1 GC)。
- 优先选择 G1 收集器(JDK 9+ 默认),或 ZGC/Shenandoah(超大内存场景),通过
-
高吞吐量场景(如后台批处理)
- 选择 Parallel Scavenge + Parallel Old 组合(JDK 8 默认),无需手动指定,或通过
-XX:+UseParallelGC启用。 - 配置吞吐量参数:
-XX:GCTimeRatio=19(目标吞吐量 95%,即 GC 时间占比不超过 5%)。
- 选择 Parallel Scavenge + Parallel Old 组合(JDK 8 默认),无需手动指定,或通过
-
客户端 / 轻量场景
- 选择 Serial + Serial Old 组合(默认),无需额外配置,适合单核 CPU 或桌面应用。
7.3 代码层面优化
-
避免内存泄漏
- 集合类泄漏:使用
ArrayList、HashMap等集合时,若不再使用,需手动清空引用(如list.clear()或list = null),避免集合持有大量无效对象。 - 静态集合风险:避免使用
static List存储大量对象(静态集合生命周期与 JVM 一致,对象无法被 GC)。 - 资源未关闭:IO 流、数据库连接、Socket 等资源需在
finally中关闭,或使用 try-with-resources 语法(JDK 7+)。
- 集合类泄漏:使用
-
减少对象创建
- 复用对象:使用对象池(如 Apache Commons Pool)复用频繁创建的对象(如数据库连接、线程)。
- 避免频繁创建短生命周期对象:如在循环中创建
String(可使用StringBuilder拼接)、临时对象。
-
合理使用引用类型
- 根据对象重要性选择引用类型,避免不必要的强引用导致对象无法回收:
- 强引用(默认):对象被强引用时,GC 不会回收(如
Object obj = new Object())。 - 软引用(SoftReference):内存不足时 GC 才会回收,适合缓存(如图片缓存)。
- 弱引用(WeakReference):GC 触发时立即回收,适合临时关联(如
WeakHashMap)。 - 虚引用(PhantomReference):仅用于跟踪对象回收,无实际引用意义。
- 强引用(默认):对象被强引用时,GC 不会回收(如
- 根据对象重要性选择引用类型,避免不必要的强引用导致对象无法回收:
更多推荐


所有评论(0)