🔥 JVM 全体系深度解析
第一部分:JVM 核心架构
1.1 JVM 整体架构图
java
┌─────────────────────────────────────────────────────────────────┐
│ JVM 虚拟机 │
│ │
│ ┌───────────────┐ ┌─────────────────────────────────────┐ │
│ │ 类加载子系统 │───→│ 运行时数据区 │ │
│ │ (ClassLoader)│ │ ┌─────────────────────────────┐ │ │
│ │ │ │ │ 线程私有 │ │ │
│ │ · 加载 │ │ │ ├─ 程序计数器 (PC) │ │ │
│ │ · 验证 │ │ │ ├─ 虚拟机栈 (VM Stack) │ │ │
│ │ · 准备 │ │ │ └─ 本地方法栈 (Native Stack) │ │ │
│ │ · 解析 │ │ ├─────────────────────────────┤ │ │
│ │ · 初始化 │ │ │ 线程共享 │ │ │
│ └───────────────┘ │ │ ├─ 堆 (Heap) │ │ │
│ │ │ │ └─ 方法区 (Method Area) │ │ │
│ ↓ │ └─────────────────────────────┘ │ │
│ ┌───────────────┐ └─────────────────────────────────────┘ │
│ │ 执行引擎 │ ↑ │
│ │ │ │ │
│ │ ┌──────────┐ │ ┌─────────────────┴──────────────────┐ │
│ │ │ 解释器 │ │───→│ 本地方法接口 (JNI) │ │
│ │ └──────────┘ │ └─────────────────┬──────────────────┘ │
│ │ ┌──────────┐ │ ↓ │
│ │ │ JIT编译器│ │ ┌─────────────────────────────────┐ │
│ │ └──────────┘ │ │ 本地方法库 │ │
│ │ ┌──────────┐ │ │ (C/C++ 编译的本地方法实现) │ │
│ │ │ GC回收器│ │ └─────────────────────────────────┘ │
│ │ └──────────┘ │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
1.2 各模块职责
| 模块 | 核心职责 | 关键组件 |
|---|---|---|
| 类加载子系统 | 加载 .class 文件到 JVM | ClassLoader、Class 文件解析器 |
| 运行时数据区 | 存储运行时数据 | 堆、栈、方法区、PC寄存器 |
| 执行引擎 | 执行字节码指令 | 解释器、JIT编译器、GC |
| 本地方法接口 | 连接 Java 与本地代码 | JNI、NATIVE 方法 |
| 本地方法库 | 提供本地方法实现 | C/C++ 编写的底层库 |
第二部分:运行时数据区(Memory Structure)
2.1 运行时数据区全景图
java
┌────────────────────────────────────────────────────────────────┐
│ JVM 进程内存 │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 堆 (Heap) - 线程共享 │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ 老年代 (Old Generation) │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │ │
│ │ │ │ 大对象区 │ │ 长期存活 │ │ 字符串 │ │ │ │
│ │ │ │ (直接进入) │ │ 对象 │ │ 常量池 │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └───────────┘ │ │ │
│ │ ├────────────────────────────────────────────────────┤ │ │
│ │ │ 新生代 (Young Generation) │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │ │
│ │ │ │ Eden区 │ │ S0区 │ │ S1区 │ │ ... │ │ │ │
│ │ │ │ (8/10) │ │ From │ │ To │ │ │ │ │ │
│ │ │ └─────────┘ └─────────┘ └─────────┘ └───────┘ │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 方法区 - 线程共享 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ 类信息 │ │ 运行时常量池 │ │ 静态变量 │ │ │
│ │ │ (类元数据) │ │ │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 直接内存 - 线程共享 │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ NIO 直接缓冲区 (DirectByteBuffer) │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 虚拟机栈 (VM Stack) - 线程私有 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │栈帧1 │ │栈帧2 │ │栈帧3 │ ... │ │
│ │ │method1 │ │method2 │ │method3 │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 本地方法栈 (Native Stack) - 线程私有 │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │native │ │native │ ... │ │
│ │ │frame1 │ │frame2 │ │ │
│ │ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 程序计数器 (PC Register) - 线程私有 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Thread1│ │ Thread2│ │ Thread3│ ... │ │
│ │ │ PC=156 │ │ PC=89 │ │ PC=203 │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
2.2 线程私有区域详解
2.2.1 程序计数器(Program Counter Register)
java
/**
* 程序计数器是最小的一块内存
* 每个线程都有自己的 PC 寄存器,互不影响
*/
public class PCRegisterDemo {
public static void main(String[] args) {
int a = 1; // PC = 0
int b = 2; // PC = 1
int c = a + b; // PC = 2
System.out.println(c); // PC = 3
}
}
特点:
- 当前线程执行的字节码行号指示器
- 分支、循环、跳转、异常处理都依赖它
- 执行 Native 方法时,PC 值为 undefined
- 唯一不会发生 OutOfMemoryError 的区域
2.2.2 虚拟机栈(VM Stack)
java
/**
* 虚拟机栈存储方法调用信息
* 每个方法调用创建一个栈帧(Stack Frame)
*/
public class StackFrameDemo {
public void methodA() {
int x = 10; // 栈帧A的局部变量
methodB(x); // 调用methodB
}
public void methodB(int x) { // 栈帧B的局部变量
long y = 20L; // 栈帧B的局部变量
methodC(); // 调用methodC
}
public void methodC() { // 栈帧C
// ...业务逻辑
}
}
栈帧结构:
java
┌─────────────────────────────────┐
│ 栈帧 (Stack Frame) │
├─────────────────────────────────┤
│ 局部变量表 (Local Variables) │
│ - 方法参数 │
│ - 方法内局部变量 │
│ - this 引用(实例方法) │
├─────────────────────────────────┤
│ 操作数栈 (Operand Stack) │
│ - 临时操作区域 │
│ - 入栈/出栈操作 │
├─────────────────────────────────┤
│ 动态链接 (Dynamic Linking) │
│ - 符号引用→直接引用 │
├─────────────────────────────────┤
│ 方法返回地址 (Return Address) │
│ - 方法正常/异常退出 │
└─────────────────────────────────┘
栈内存异常:
java
# StackOverflowError:栈深度超出限制
# 示例:递归没有终止条件
public class StackOverflowDemo {
public static void recursion() {
recursion(); // 无限递归 → StackOverflowError
}
public static void main(String[] args) {
recursion();
}
}
# 配置栈大小
java -Xss2m StackOverflowDemo # 2MB栈内存
java -Xss512k StackOverflowDemo # 512KB栈内存
2.2.3 本地方法栈(Native Method Stack)
- 为 JVM 执行 Native 方法(C/C++)提供服务
- 与虚拟机栈类似,但服务对象不同
- HotSpot 将两者合二为一
java
/**
* 本地方法示例
* Object 类的大量方法都是 native 方法
*/
public class NativeMethodDemo {
// Object 类中的 native 方法
public native void notify();
public native void notifyAll();
public native void wait(long timeout);
// HashMap 的 native 实现
public native int hashCode();
}
2.3 线程共享区域详解
2.3.1 堆(Heap)
java
/**
* 堆是 JVM 最大的一块内存
* 所有对象实例和数组都在堆中分配
*/
public class HeapDemo {
public static void main(String[] args) {
// 对象在堆中分配
User user = new User();
// 数组在堆中分配
int[] arr = new int[1024];
// 包装类和字符串常量也在堆中
Integer i = 100; // 自动装箱,堆中分配
String s = "hello"; // 字符串常量池
}
}
JDK7/8 堆内存对比:
java
JDK7 堆结构:
┌────────────────────────────────────┐
│ 堆 │
│ ┌──────┐ ┌───────┐ ┌──────────┐ │
│ │ Eden │ │S0/S1 │ │ Old Gen │ │
│ │ │ │ │ │ 永久代 │ │
│ │ │ │ │ │(类信息等) │ │
│ └──────┘ └───────┘ └──────────┘ │
└────────────────────────────────────┘
JDK8 堆结构:
┌────────────────────────────────────┐
│ 堆 │
│ ┌──────┐ ┌───────┐ ┌──────────┐ │
│ │ Eden │ │S0/S1 │ │ Old Gen │ │
│ │ │ │ │ │ │ │
│ └──────┘ └───────┘ └──────────┘ │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 元空间 (MetaSpace) │
│ (本地内存,不再占用堆空间) │
└────────────────────────────────────┘
JDK8 元空间变化:
| 对比项 | JDK7 永久代 | JDK8 元空间 |
|---|---|---|
| 位置 | 堆内存 | 本地内存(操作系统内存) |
| 大小 | 固定,难以调整 | 动态扩展,受物理内存限制 |
| 字符串常量池 | 永久代 | 移至堆 |
| 类元数据 | 永久代 | 元空间 |
| OOM风险 | 容易 Full GC | 很少 OOM |
2.3.2 方法区(Method Area)
java
/**
* 方法区存储:
* 1. 类元信息(类名、修饰符、父类等)
* 2. 运行时常量池
* 3. 静态变量(JDK8 移至堆)
* 4. 方法字节码
*/
public class MethodAreaDemo {
// 静态变量 → 方法区(JDK8移至堆)
public static int staticVar = 100;
// 常量 → 运行时常量池
public static final int CONSTANT = 200;
// 方法信息 → 方法区
public void method() {
String s = "hello"; // 字符串常量池
}
}
2.4 直接内存(Direct Memory)
java
/**
* 直接内存不受 JVM 堆大小限制
* 用于 NIO 的 DirectByteBuffer
* 减少 Java 堆和本地堆之间的数据复制
*/
public class DirectMemoryDemo {
public static void main(String[] args) {
// 分配直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100); // 100MB
// 适用场景:
// 1. 大文件读写
// 2. 网络通信
// 3. 高性能缓存
}
}
配置直接内存大小:
-XX:MaxDirectMemorySize=512m
第三部分:类加载子系统(Class Loader)
3.1 类加载完整流程
java
类加载的 5 个阶段
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
加载 (Loading) 验证 (Verification)
┌────────────────┐ ┌────────────────┐
│ 1. 通过类的 │ │ 1. 文件格式验证 │
│ 全限定名 │ │ (魔数检查) │
│ 获取.class │ → │ 2. 元数据验证 │
│ 文件字节流 │ │ (语义分析) │
│ │ │ 3. 字节码验证 │
│ 2. 将字节流 │ │ (指令验证) │
│ 转为方法区 │ │ 4. 符号引用验证 │
│ 的类元数据 │ │ (类型检查) │
│ │ └────────────────┘
│ 3. 在堆中生成 │ ↑
│ Class对象 │ │
│ 作为入口 │ │
└────────────────┘ │
│ │
↓ │
准备 (Preparation) │
┌────────────────┐ │
│ 1. 为静态变量 │ │
│ 分配内存 │ │
│ │ ┌──────┴────────┐
│ 2. 设置默认值 │ → │ 解析 │
│ (int→0) │ │ (Resolution) │
│ │ ├────────────────┤
│ 3. 不执行 │ │ 将符号引用转换 │
│ 赋值语句 │ │ 为直接引用 │
│ │ │ │
└────────────────┘ │ 1. 类/接口解析 │
│ 2. 字段解析 │
│ 3. 方法解析 │
│ 4. 接口方法解析 │
└────────────────┘
↑
初始化 (Initialization) │
┌────────────────┐ │
│ 1. 执行<clinit>│──────────────┘
│ ()方法 │
│ (静态代码块) │
│ │
│ 2. 真正执行 │
│ 赋値语句 │
│ (int a=10) │
│ │
│ 3. 线程安全 │
│ (JVM保证) │
└────────────────┘
3.2 类加载时机(主动使用)
java
/**
* 类加载的触发时机(主动使用)
*/
public class ClassLoadingTriggers {
// 触发1:创建类的实例
public void trigger1() {
User user = new User(); // 触发加载
}
// 触发2:调用类的静态方法
public void trigger2() {
User.staticMethod(); // 触发加载
}
// 触发3:访问类的静态变量
public void trigger3() {
int x = User.staticVar; // 触发加载
}
// 触发4:反射
public void trigger4() throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.example.User"); // 触发加载
}
// 触发5:初始化子类,父类先初始化
public void trigger5() {
// Parent 会先被加载
Child child = new Child();
}
// 触发6:启动类(main方法所在类)
public static void main(String[] args) {
// main方法所在类会被加载
}
}
3.3 类加载器层次结构
java
类加载器层次
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Bootstrap ClassLoader
(启动类加载器)
┌──────────────────┐
│ 加载 Java 核心类 │
│ $JAVA_HOME/jre/ │
│ lib/rt.jar │
│ tools.jar │
│ resources.jar │
└────────┬─────────┘
│ 父加载器为 null
↓
┌───────────────────────────┐
│ Extension ClassLoader │
│ (扩展类加载器) │
│ $JAVA_HOME/jre/lib/ext/* │
└─────────────┬─────────────┘
│ 父加载器为 Bootstrap
↓
┌─────────────────────────────────────┐
│ Application ClassLoader │
│ (应用类加载器) │
│ 加载 classpath 下的所有类 │
│ -XX:+TraceClassLoading 观察加载 │
└─────────────┬───────────────────────┘
│ 父加载器为 Extension
↓
┌──────────────────┐
│ CustomClassLoader │
│ (自定义类加载器) │
│ 可打破双亲委派 │
└──────────────────┘
3.4 双亲委派模型(Parent Delegation Model)
java
/**
* 双亲委派模型工作流程
*
* 当类加载器收到加载请求时:
* 1. 先委派给父类加载器处理
* 2. 父类无法加载时,才自己加载
*/
public class ParentDelegationDemo {
public static void main(String[] args) {
// 加载 java.lang.String
// 1. Application ClassLoader 收到请求
// 2. 委派给 Extension ClassLoader
// 3. Extension 委派给 Bootstrap ClassLoader
// 4. Bootstrap 找到了 java.lang.String
// 5. 自己加载,返回 Class 对象
String s = "hello";
// 不可能自定义一个 java.lang.String
// 因为 Bootstrap 已经加载了核心类
}
}
工作流程图:
java
类加载请求
│
↓
┌─────────────────────────────┐
│ Application ClassLoader │
│ 收到加载 "com.example.User"│
└─────────────┬───────────────┘
│ 委派给父加载器
↓
┌─────────────────────────────┐
│ Extension ClassLoader │
│ 收到加载 "com.example.User"│
└─────────────┬───────────────┘
│ 委派给父加载器
↓
┌─────────────────────────────┐
│ Bootstrap ClassLoader │
│ 收到加载 "com.example.User"│
└─────────────┬───────────────┘
│ 无法加载
↓
┌─────────────────────────────┐
│ Extension ClassLoader │
│ 尝试加载 "com.example.User"│
│ ✓ 在 ext 目录下找到并加载 │
└─────────────────────────────┘
双亲委派的优势:
- 避免类重复加载:父类已加载,子类无需再加载
- 安全性保障 :防止核心类被篡改(如自定义
java.lang.String)
3.5 破坏双亲委派模型
场景1:JDBC 驱动加载(线程上下文类加载器)
java
/**
* JDBC 驱动的破坏场景
*
* JDBC API 在 rt.jar 中,由 Bootstrap 加载
* 但 JDBC 驱动(如 MySQL、Oracle)由第三方实现
* 驱动在 classpath 外,无法被父加载器加载
*/
public class JDBCDelegationBreak {
public static void main(String[] args) {
// 这种方式需要先手动加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 但 SPI 机制通过线程上下文类加载器解决
// ServiceLoader 使用 Thread.currentThread().getContextClassLoader()
}
}
场景2:TOMCAT 类隔离
java
/**
* TOMCAT 的类隔离机制
*
* 一个服务器运行多个 Web 应用
* 每个应用可能依赖不同版本的同一个类
* 必须打破双亲委派,实现应用隔离
*/
public class TomcatIsolation {
// Tomcat 的类加载器结构:
// 1. Bootstrap ClassLoader
// 2. System ClassLoader
// 3. Common ClassLoader (共享库)
// 4. WebApp ClassLoader (每个Web应用独立)
// ↑
// 打破了双亲委派!
}
场景3:OSGi 模块化
java
/**
* OSGi 的类加载机制
*
* 每个 Bundle(模块)有自己的类加载器
* 可以精确控制类的可见性
* 完全打破双亲委派
*/
public class OSGiBreak {
// OSGi 加载顺序:
// 1. Bundle 自身类加载器
// 2. Import Package 类加载器
// 3. Require Bundle 类加载器
// 4. Bootstrap ClassLoader
}
3.6 自定义类加载器
java
/**
* 自定义类加载器示例
* 应用场景:热部署、加密类加载、类隔离
*/
public class CustomClassLoader extends ClassLoader {
private String basePath;
public CustomClassLoader(String basePath) {
this.basePath = basePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 将类名转换为文件路径
String fileName = name.replace('.', '/') + ".class";
String fullPath = basePath + fileName;
// 2. 读取字节流
try (FileInputStream fis = new FileInputStream(fullPath);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// 3. 调用 defineClass 将字节流转为 Class 对象
byte[] classData = bos.toByteArray();
return defineClass(name, classData, 0, classData.length);
} catch (IOException e) {
throw new ClassNotFoundException("Class not found: " + name, e);
}
}
// 重写 loadClass 打破双亲委派
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 优先自己加载(打破双亲委派)
if (name.startsWith("com.myapp.")) {
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
return c;
}
// 其他类走双亲委派
return super.loadClass(name);
}
public static void main(String[] args) throws Exception {
CustomClassLoader loader = new CustomClassLoader("/path/to/classes/");
Class<?> clazz = loader.loadClass("com.myapp.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println("自定义类加载成功: " + obj.getClass().getClassLoader());
}
}
第四部分:垃圾回收机制(GC)
4.1 对象存活判断算法
4.1.1 引用计数法(已废弃)
java
/**
* 引用计数法
*
* 对象每被引用一次,计数器+1
* 引用断开时,计数器-1
* 计数器为0时,可回收
*
* 问题:无法解决循环引用
*/
public class ReferenceCounting {
public static void main(String[] args) {
// objA 和 objB 相互引用,但都没有被外部引用
// 引用计数都为1,但实际上已经不可达
objA.ref = objB;
objB.ref = objA;
objA = null;
objB = null;
// GC 无法回收这两个对象(循环引用问题)
}
}
4.1.2 可达性分析算法(GC Roots)
java
/**
* 可达性分析算法
*
* 从 GC Roots 出发,向下搜索
* 能到达的对象是存活的
* 不能到达的对象可回收
*/
public class GC RootsDemo {
// GC Roots 包括:
// 1. 虚拟机栈中引用的对象
public GCRootsDemo obj1 = new GCRootsDemo();
public void method() {
// 局部变量表中的对象引用
Object local = new Object();
}
// 2. 方法区中静态变量引用的对象
public static Object staticObj = new Object();
// 3. 方法区中常量引用的对象
public static final Object CONSTANT_OBJ = new Object();
// 4. 本地方法栈中 JNI 引用的对象
// native 方法持有的对象引用
public static void main(String[] args) {
// local 变量是 GC Root
// staticObj 是 GC Root
// 常量引用是 GC Root
}
}
GC Roots 完整列表:
java
GC Roots 包含:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 虚拟机栈中引用的对象
- 方法中局部变量表引用的对象
- 栈帧中操作数栈引用的对象
2. 本地方法栈中 JNI 引用的对象
- native 方法中引用的对象
3. 方法区中类静态属性引用的对象
- static 修饰的引用类型变量
4. 方法区中常量引用的对象
- static final 修饰的引用类型常量
5. JVM 内置类加载器引用的对象
- Class 对象等
6. 正在持有的锁对象
- synchronized 锁住的对象
7. JVM 内部引用
- 异常对象、类加载器等
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4.2 引用类型详解
java
/**
* 四种引用类型及回收时机
*/
public class ReferenceTypes {
// 1. 强引用 - 最常见的引用
// 无论内存是否充足,都不会被回收
Object strongRef = new Object(); // GC 不会回收
// 2. 软引用 - 内存不足时回收
// SoftReference 用于缓存
SoftReference<Object> softRef = new SoftReference<>(new Object());
// 3. 弱引用 - 下次 GC 时回收
// WeakReference 用于防止内存泄漏
WeakReference<Object> weakRef = new WeakReference<>(new Object());
// 4. 虚引用 - 随时可能被回收
// 必须配合 ReferenceQueue,用于跟踪对象回收
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
}
引用类型对比:
| 引用类型 | 回收时机 | 典型应用 |
|---|---|---|
| 强引用 | 永不回收 | 普通对象引用 |
| 软引用 | 内存不足时 | 图片缓存 |
| 弱引用 | 下次 GC | ThreadLocal Map |
| 虚引用 | 随时可回收 | NIO 直接内存管理 |
4.3 垃圾回收算法
4.3.1 标记-清除算法(Mark-Sweep)
java
┌─────────────────────────────────────────┐
│ 标记-清除算法 │
│ │
│ 步骤1:标记所有需要回收的对象 │
│ ┌───────────────────────────────┐ │
│ │ 存活对象:√ 死亡对象:× │ │
│ │ │ │
│ │ □ □ □ □ □ □ □ □ □ □ │ │
│ │ √ × √ √ × √ × √ √ × │ │
│ │ │ │
│ └───────────────────────────────┘ │
│ │
│ 步骤2:清除死亡对象 │
│ ┌───────────────────────────────┐ │
│ │ 清除后(产生内存碎片) │ │
│ │ │ │
│ │ □ □ □ □ □ □ □ □ □ │ │
│ │ (空闲) (空闲) │ │
│ │ 无法分配大对象 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────────┘
优点:实现简单
缺点:内存碎片、效率不稳定
4.3.2 复制算法(Copying)
java
┌─────────────────────────────────────────┐
│ 复制算法 │
│ │
│ 将内存分为两块,每次只用一块 │
│ │
│ 步骤1:使用区 A │
│ ┌───────────────────────────────┐ │
│ │ 区域 A(使用中) │ │
│ │ 存活对象 │ │
│ │ │ │
│ │ √ □ √ □ □ √ □ □ □ □ │ │
│ └───────────────────────────────┘ │
│ │
│ 步骤2:存活对象复制到区域 B │
│ ┌─────────────────┐ ┌───────────────┐ │
│ │ 区域 A(清空) │ │ 区域 B │ │
│ │ │ │ 复制存活对象 │ │
│ │ │ │ √ √ √ │ │
│ └─────────────────┘ └───────────────┘ │
│ │
│ 步骤3:交换使用区域 │
│ ┌─────────────────┐ ┌───────────────┐ │
│ │ 区域 A │ │ 区域 B │ │
│ │ │ │ 复制存活对象 │ │
│ │ │ │ √ √ √ │ │
│ └─────────────────┘ └───────────────┘ │
└─────────────────────────────────────────┘
优点:无内存碎片、效率高
缺点:内存利用率低(50%)
适用:新生代(对象存活率低)
4.3.3 标记-整理算法(Mark-Compact)
java
┌─────────────────────────────────────────┐
│ 标记-整理算法 │
│ │
│ 步骤1:标记存活对象 │
│ ┌───────────────────────────────┐ │
│ │ √ × √ √ × √ × √ × √ │ │
│ └───────────────────────────────┘ │
│ │
│ 步骤2:整理,存活对象移向一端 │
│ ┌───────────────────────────────┐ │
│ │ 存活对象 │ 空闲区域 │ │
│ │ √ √ √ √ √ √ │ × × × × │ │
│ │ ↑ │ │
│ │ 移动到这里 │ │
│ └───────────────────────────────┘ │
│ │
│ 优点:无碎片、内存利用率高 │
│ 缺点:移动对象开销大、STW时间长 │
│ 适用:老年代 │
└─────────────────────────────────────────┘
4.4 分代收集理论
java
分代收集策略
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌────────────────────────────────────────────────────────┐
│ 堆内存 │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ 新生代 │ │ 老年代 │ │
│ │ Young Generation │ │ Old Generation │ │
│ │ │ │ │ │
│ │ ┌────┐┌────┐┌────┐ │ │ ┌───────────────────┐ │ │
│ │ │Eden││ S0 ││ S1 │ │ │ │ │ │ │
│ │ │ 8份 ││ 1份 ││ 1份│ │ │ │ │ │ │
│ │ └────┘└────┘└────┘ │ │ │ 存活率高的对象 │ │ │
│ │ │ │ │ │ │ │
│ │ ┌──────────────┐ │ │ │ │ │ │
│ │ │ Minor GC区域 │ │ │ │ │ │ │
│ │ └──────────────┘ │ │ └───────────────────┘ │ │
│ │ │ │ │ │
│ │ 复制算法 │ │ 标记-整理/标记-清除 │ │
│ │ 对象存活率低 │ │ 对象存活率高 │ │
│ │ 频繁收集 │ │ 偶尔收集 │ │
│ └─────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ 元空间(本地内存) │ │
│ │ 类信息、常量等 │ │
│ └────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
对象流转过程
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
new User()
│
↓ 分配到 Eden 区
┌─────────┐
│ Eden │ ←── 通常新对象在这里分配
└────┬────┘
│ Minor GC 后,存活对象移入 S0
↓
┌────┴────┐
│ S0 或 S1 │ ←── 每次 Minor GC 后,交换使用
└────┬────┘
│ 多次 Minor GC 后,对象进入老年代
↓
┌─────────┐
│ Old │ ←── 长期存活的对象
└─────────┘
│
│ 触发条件
↓
┌─────────┐
│ Full │ ←── 整个堆+元空间
│ GC │
└─────────┘
4.5 垃圾收集器详解
4.5.1 收集器全景图
java
JVM 垃圾收集器家族
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
新生代 老年代
┌──────┐ ┌──────────┐
│ │ │ │
┌────────────┤Serial├────────────┐ │ │
│ │ │ │ │ │
│ └──┬───┘ │ │ │
↓ ↓ ↓ │ │
┌──────┐ ┌──────────┐ ┌────────────┐ │ │
│ParNew│ │ Parallel │ │ Parallel │ │ │
│ │ │ Scavenge │ │ Old │ │ │
└──┬───┘ └──────────┘ └─────┬──────┘ │ │
│ │ │ │
│ │ │ │
↓ ↓ │ │
┌──────┐ ┌────────┐ │ │
│ CMS │←───────────────────────────│ G1 │←───────┤ │
│ │ │ │ │ │
└──┬───┘ └───┬────┘ │ │
│ │ │ │
│ │ │ │
↓ ↓ ↓ │
┌────────┐ ┌────────┐ ┌──────────┐ │
│ Serial │ │ ZGC │ │Shenandoah│ │
│ Old │ │ │ │ │ │
└────────┘ └────────┘ └──────────┘ │
│
↓
┌──────────┐
│ Epsilon │
│ (无操作) │
└──────────┘
年轻代收集器组合(红色箭头)
老年代收集器组合(蓝色箭头)
4.5.2 Serial 收集器
java
/**
* Serial 收集器
*
* 特点:
* - 单线程收集
* - GC 时暂停所有用户线程(Stop The World)
* - 简单高效,适合客户端应用
*/
public class SerialCollector {
// 启用参数:-XX:+UseSerialGC
// 新生代:Serial(复制算法)
// 老年代:Serial Old(标记-整理)
}
4.5.3 ParNew 收集器
java
/**
* ParNew 收集器
*
* 特点:
* - Serial 的多线程版本
* - 默认线程数 = CPU 核心数
* - 响应优先,配合 CMS 使用
*/
public class ParNewCollector {
// 启用参数:-XX:+UseParNewGC
// 新生代:ParNew(复制算法)
// 老年代:CMS 或 Serial Old
}
4.5.4 Parallel Scavenge 收集器
java
/**
* Parallel Scavenge 收集器
*
* 特点:
* - 吞吐量优先
* - 可控制最大停顿时间和吞吐量
* - 自适应调节策略
*/
public class ParallelScavengeCollector {
// 启用参数:-XX:+UseParallelGC
// 新生代:Parallel Scavenge(复制算法)
// 老年代:Parallel Old(标记-整理)
// 关键参数:
// -XX:MaxGCPauseMillis=100 // 最大停顿时间
// -XX:GCTimeRatio=99 // 吞吐量 = 99%
// -XX:+UseAdaptiveSizePolicy // 自适应调节
}
4.5.5 CMS 收集器(Concurrent Mark Sweep)
java
/**
* CMS 收集器
*
* 目标:低停顿、低延迟
* 算法:标记-清除
*
* 工作流程:
* 1. 初始标记(STW)- 标记 GC Roots 直接关联的对象
* 2. 并发标记 - 遍历 GC Roots 引用链
* 3. 重新标记(STW)- 修正并发标记期间的变化
* 4. 并发清除 - 清理死亡对象
*/
public class CMSCollector {
// 启用参数:-XX:+UseConcMarkSweepGC
// 新生代:ParNew
// 老年代:CMS
// 问题:
// 1. 内存碎片(标记-清除)
// 2. 并发阶段占用 CPU
// 3. 无法处理浮动垃圾(并发时新产生的垃圾)
// 参数调优:
// -XX:CMSInitiatingOccupancyFraction=70 // 老年代使用率70%时触发
// -XX:+UseCMSCompactAtFullCollection // FullGC时整理
// -XX:CMSFullGCsBeforeCompaction=5 // 5次FullGC后整理
}
CMS 工作流程图:
java
CMS 工作流程
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
初始标记 (STW)
├─ 停顿时间:短
└─ 标记:GC Roots 直接关联的对象
│
↓
┌─────────────┐
│ 并发标记 │ ←── 用户线程并发执行
├─────────────┤
│ 遍历GC Roots│
│ 引用链 │
│ 标记存活对象│
└──────┬──────┘
│ 用户线程继续运行
↓ 产生新的垃圾(浮动垃圾)
重新标记 (STW)
├─ 停顿时间:较长
└─ 修正并发标记期间产生的变化
│
↓
┌─────────────┐
│ 并发清除 │ ←── 用户线程并发执行
├─────────────┤
│ 清除死亡 │
│ 对象 │
└─────────────┘
4.5.6 G1 收集器(Garbage First)
java
/**
* G1 收集器
*
* 思想:将堆划分为多个大小相等的 Region
* 优先回收价值最高的 Region(垃圾最多)
* 兼顾吞吐量和低延迟
*
* 特点:
* - 面向服务端应用
* - 可以设置期望停顿时间(-XX:MaxGCPauseMillis)
* - 整合多种算法(标记-整理、复制)
* - 没有内存碎片
*/
public class G1Collector {
// 启用参数:-XX:+UseG1GC
// Region 结构:
// - Eden Region(E)
// - Survivor Region(S)
// - Old Region(O)
// - Humongous Region(H)// 大对象,超过Region一半的对象
// G1 的工作流程:
// 1. Young GC:收集年轻代的 Eden/Survivor
// 2. Mixed GC:年轻代 + 价值最高的老年代 Region
// 关键参数:
// -XX:MaxGCPauseMillis=200 // 最大停顿时间
// -XX:G1NewSizePercent=5 // 年轻代最小比例
// -XX:G1MaxNewSizePercent=60 // 年轻代最大比例
}
G1 Region 结构:
java
G1 堆内存布局
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────────────────────┐
│ 堆内存 │
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ E │ │ E │ │ S │ │ O │ │ O │ │ H │ │ O │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ E │ │ E │ │ O │ │ O │ │ O │ │ E │ │ E │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ O │ │ O │ │ E │ │ E │ │ S │ │ O │ │ O │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
│ E = Eden Region S = Survivor Region │
│ O = Old Region H = Humongous Region │
│ │
│ 每个 Region 大小:1MB - 32MB(2的幂次) │
│ 整个堆最多 2048 个 Region │
└─────────────────────────────────────────────────────────┘
4.5.7 ZGC 收集器
java
/**
* ZGC(Z Garbage Collector)
*
* 目标:亚毫秒级停顿,最大停顿时间不超过 10ms
* 算法:并发标记 + 增量更新 + 读屏障
* 特点:
* - 不分代(但有分代思想)
* - 着色指针(Colored Pointers)
* - 读屏障(Load Barrier)
* - 支持 TB 级堆内存
*/
public class ZGCCollector {
// 启用参数:JDK 11+ -XX:+UseZGC
// 参数:
// -XX:MaxGCPauseMillis=10 // 最大停顿10ms
// -XX:ConcGCThreads=4 // 并发GC线程数
}
4.5.8 Shenandoah 收集器
java
/**
* Shenandoah
*
* 目标:低延迟
* 类似 ZGC,但使用转发指针(Brooks Pointer)
* 支持 OpenJDK
*/
public class ShenandoahCollector {
// 启用参数:-XX:+UseShenandoahGC
}
4.5.9 收集器对比总结
| 收集器 | 工作区域 | 算法 | 并发 | 停顿时间 | 吞吐量 | 适用场景 |
|---|---|---|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程 | 长(STW) | 低 | 客户端、单核 |
| ParNew | 新生代 | 复制 | 多线程 | 短 | 中 | 配合CMS |
| Parallel Scavenge | 新生代 | 复制 | 多线程 | 可调节 | 高 | 后台批处理 |
| Serial Old | 老年代 | 标记-整理 | 单线程 | 长 | 低 | 客户端 |
| Parallel Old | 老年代 | 标记-整理 | 多线程 | 可调节 | 高 | 后台批处理 |
| CMS | 老年代 | 标记-清除 | 并发 | 短 | 中 | 互联网应用 |
| G1 | 整堆 | 标记-整理+复制 | 并发 | 可预测 | 中高 | 替代CMS |
| ZGC | 整堆 | 标记-整理 | 并发 | <10ms | 高 | 大内存低延迟 |
| Shenandoah | 整堆 | 标记-整理 | 并发 | 低 | 高 | 低延迟 |
第五部分:字节码与执行引擎
5.1 字节码文件结构
java
/**
* 字节码文件结构
*/
public class BytecodeStructure {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a + b;
System.out.println(c);
}
}
字节码结构图:
java
Class 文件结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────────────────────┐
│ 魔数 (Magic Number) │
│ 0xCAFEBABE - 标识这是 Java class 文件 │
├─────────────────────────────────────────────────────────┤
│ 主次版本号 (Major/Minor Version) │
│ 52.0 = JDK 8 │
├─────────────────────────────────────────────────────────┤
│ 常量池 (Constant Pool) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ #1 Method │ │ #2 Class │ │ #3 UTF8 │ │
│ │ref: #2 #4 │ │name: #3 │ │"main" │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 访问标志 (Access Flags) │
│ ACC_PUBLIC | ACC_SUPER (0x0001 | 0x0020) │
├─────────────────────────────────────────────────────────┤
│ 类索引 (This Class) - 指向常量池 │
│ #1 = com/example/BytecodeStructure │
├─────────────────────────────────────────────────────────┤
│ 父类索引 (Super Class) - 指向常量池 │
│ #3 = java/lang/Object │
├─────────────────────────────────────────────────────────┤
│ 接口表 (Interfaces) │
│ 空(没有实现接口) │
├─────────────────────────────────────────────────────────┤
│ 字段表 (Fields) │
│ 空(没有字段) │
├─────────────────────────────────────────────────────────┤
│ 方法表 (Methods) │
│ ┌────────────────────────────────────────────────┐ │
│ │ main 方法 │ │
│ │ ┌──────┐ ┌────────────┐ ┌─────────────────┐ │ │
│ │ │ 权限 │ │ 方法名 #4 │ │ 描述符 (#5) │ │ │
│ │ │ACC │ │ "main" │ │ "([Ljava/lang/ │ │ │
│ │ │PUBLIC│ │ │ │ String;)V" │ │ │
│ │ │STATIC│ │ │ │ │ │ │
│ │ └──────┘ └────────────┘ └─────────────────┘ │ │
│ │ Code 属性:字节码指令 │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ bipush 10 // 将 10 压入栈 │ │ │
│ │ │ istore_1 // 存储到局部变量表 slot1 │ │ │
│ │ │ bipush 20 // 将 20 压入栈 │ │ │
│ │ │ istore_2 // 存储到局部变量表 slot2 │ │ │
│ │ │ iload_1 // 加载 slot1 的值 │ │ │
│ │ │ iload_2 // 加载 slot2 的值 │ │ │
│ │ │ iadd // 相加 │ │ │
│ │ │ istore_3 // 存储到局部变量表 slot3 │ │ │
│ │ │ getstatic #6 // 获取 System.out │ │ │
│ │ │ iload_3 // 加载 slot3 的值 │ │ │
│ │ │ invokevirtual #7 // 调用 println │ │ │
│ │ │ return // 返回 │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 属性表 (Attributes) │
│ SourceFile、LineNumberTable 等 │
└─────────────────────────────────────────────────────────┘
5.2 常见字节码指令
java
/**
* 字节码指令分类
*/
public class BytecodeInstructions {
// ============ 加载存储指令 ============
public void loadStore() {
int a = 10; // bipush 10 / istore_1
int b = a + 20; // iload_1 / bipush 20 / iadd / istore_2
}
// iload_n: 从局部变量表加载 int 到栈
// istore_n: 从栈存储 int 到局部变量表
// bipush: 加载 byte 常量
// sipush: 加载 short 常量
// iconst_0~5: 加载 int 常量 0~5
// ============ 运算指令 ============
public int arithmetic() {
int a = 10, b = 20;
return a + b; // iadd
}
public int multiply() {
int a = 5, b = 6;
return a * b; // imul
}
// iadd, isub, imul, idiv, irem
// iand, ior, ixor // 按位与、或、异或
// ============ 类型转换 ============
public void typeCast() {
int a = 10;
long b = a; // i2l
double c = a; // i2d
}
// i2l, i2f, i2d, l2i, l2f, l2d 等
// ============ 对象创建与访问 ============
public Object createObject() {
Object obj = new Object(); // new / dup / invokespecial
return obj;
}
// new: 创建对象
// dup: 复制栈顶值
// getfield: 获取字段
// putfield: 设置字段
// ============ 方法调用 ============
public void methodInvoke() {
methodA(); // invokevirtual
}
public static void methodA() {} // invokestatic
// invokevirtual: 调用虚方法(多态)
// invokestatic: 调用静态方法
// invokespecial: 调用构造器/私有方法/父类方法
// invokedynamic: 调用动态方法(Lambda)
// ============ 控制转移 ============
public void controlTransfer() {
int a = 10;
if (a > 5) { // ifle 标签 / goto 标签
System.out.println("a > 5");
}
for (int i = 0; i < 10; i++) {} // 循环
}
// ifeq, ifne, iflt, ifgt, ifle, if_icmpXX
// goto, goto_w
// tableswitch, lookupswitch
// ============ 异常处理 ============
public void exception() {
try {
throw new RuntimeException();
} catch (RuntimeException e) {
// athrow
}
}
// athrow: 抛出异常
// ============ 同步指令 ============
public synchronized void sync() {
synchronized (this) {
// monitorenter / monitorexit
}
}
// monitorenter: 进入同步块
// monitorexit: 退出同步块
}
5.3 执行引擎工作原理
java
执行引擎架构
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────────────────────────────┐
│ 字节码 (.class) │
└─────────────────────────┬───────────────────────────────┘
↓
┌───────────────────────┐
│ 解释器 │
│ (Interpreter) │
│ │
│ 逐行解释执行 │
│ 启动快,执行慢 │
└───────────┬───────────┘
│
↓
┌───────────────────────────────────────────┐
│ 热点代码探测 │
│ ┌─────────────────────────────────────┐ │
│ │ 方法调用计数器 │ │
│ │ 循环回边计数器 │ │
│ │ │ │
│ │ 阈值:10000次 或 热点探测 │ │
│ └─────────────────────────────────────┘ │
└───────────────────────┬───────────────────┘
│
↓ 热点代码达到阈值
┌───────────────────────┐
│ JIT 编译器 │
│ (Just-In-Time) │
│ │
│ 编译为本地机器码 │
│ 执行快,启动慢 │
│ │
│ ┌──────────────────┐ │
│ │ C1 编译器 │ │
│ │ 客户端优化 │ │
│ └──────────────────┘ │
│ ┌──────────────────┐ │
│ │ C2 编译器 │ │
│ │ 服务端优化 │ │
│ └──────────────────┘ │
└───────────┬───────────┘
│
↓
┌───────────────────────┐
│ 本地机器码 │
│ (Native Code) │
└───────────────────────┘
5.4 JIT 编译器优化技术
java
/**
* JIT 编译器的优化技术
*/
public class JITOptimization {
// 1. 方法内联 (Method Inlining)
// 将被调用方法直接展开到调用处,减少调用开销
public void inlineDemo() {
// 编译器会内联 add 方法
int result = add(1, 2);
}
private int add(int a, int b) {
return a + b;
}
// 内联后:
// int result = 1 + 2;
// 2. 逃逸分析 (Escape Analysis)
// 分析对象的动态作用域
public void escapeDemo() {
// 锁消除:methodSync 未逃逸,锁可消除
methodSync();
}
private synchronized void methodSync() {
// 锁对象未逃逸,安全消除锁
}
// 3. 栈上分配 (Stack Allocation)
// 未逃逸的对象直接在栈上分配,不进入堆
public void stackAllocDemo() {
// point 未逃逸,栈上分配
Point p = new Point(1, 2);
System.out.println(p.x);
}
// 4. 标量替换 (Scalar Replacement)
// 将对象拆解为原始类型,减少对象分配
public void scalarDemo() {
// 编译器可能直接用 x=1, y=2 替代 Point 对象
Point p = new Point(1, 2);
use(p.x, p.y);
}
// 5. 公共子表达式消除
public void cseDemo() {
int a = (b + c) + (b + c); // 优化为 tmp = b + c; a = tmp + tmp
}
// 6. 数组边界检查消除
public void boundsCheckElimination() {
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = i; // JIT 判断 i 永远不会越界,消除边界检查
}
}
static class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
static void use(int x, int y) {}
}
第六部分:实战调优
6.1 JVM 参数大全
java
# ==================== 内存参数 ====================
# 堆内存
-Xms512m # 堆初始大小
-Xmx512m # 堆最大大小(生产环境建议与Xms相同)
-Xmn128m # 新生代大小
-XX:NewRatio=2 # 新生代:老年代 = 1:2
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1
# 元空间(JDK8+)
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间最大大小
# 线程栈
-Xss1m # 线程栈大小(默认1MB)
# 直接内存
-XX:MaxDirectMemorySize=512m # 直接内存最大大小
# ==================== 垃圾收集器参数 ====================
# Serial
-XX:+UseSerialGC
# ParNew + CMS
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
# Parallel
-XX:+UseParallelGC # 新生代 Parallel Scavenge
-XX:+UseParallelOldGC # 老年代 Parallel Old
-XX:ParallelGCThreads=4 # GC 线程数
# G1
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 最大停顿时间
-XX:G1NewSizePercent=5 # 年轻代最小比例
-XX:G1MaxNewSizePercent=60 # 年轻代最大比例
-XX:InitiatingHeapOccupancyPercent=45 # 触发Mixed GC的堆使用率
# ZGC(JDK 11+)
-XX:+UseZGC
-XX:MaxGCPauseMillis=10
# ==================== GC 日志参数 ====================
# JDK 9+ 推荐格式
-Xlog:gc*:file=gc.log:time,uptime,level,tags
# JDK 8 格式
-Xloggc:gc.log
-verbose:gc
-XX:+PrintGCDetails # 打印详细GC日志
-XX:+PrintGCDateStamps # 打印时间戳
-XX:+PrintGCTimeStamps # 打印GC发生时间
-XX:+PrintTenuringDistribution # 打印年龄分布
-XX:+PrintReferenceGC # 打印 Reference 处理时间
# ==================== OOM 参数 ====================
-XX:+HeapDumpOnOutOfMemoryError # OOM时导出堆
-XX:HeapDumpPath=/path/to/dump.hprof # 堆转储路径
-XX:+ExitOnOutOfMemoryError # OOM时退出(JDK 8u92+)
-XX:+CrashOnOutOfMemoryError # OOM时crash(JDK 8u94+)
# ==================== 性能参数 ====================
-XX:+AlwaysPreTouch # 启动时预分配并访问所有堆内存
-XX:PrefetchLines=2 # 预取行数
-XX:+UseStringDeduplication # 字符串去重(JDK 8u20+)
-XX:+OptimizeStringConcat # 优化字符串拼接
# ==================== JIT 参数 ====================
-XX:CompileThreshold=10000 # 方法调用阈值触发JIT
-XX:TieredCompilation # 分层编译(JDK 7u40+)
-XX:+TieredCompilation # 启用分层编译
# ==================== 调试参数 ====================
-XX:+PrintCommandLineFlags # 打印显式设置的参数
-XX:+PrintVMOptions # 打印传递给JVM的参数
-verbose:class # 打印类加载信息
-XX:+TraceClassLoading # 跟踪类加载
-XX:+UnlockCommercialVMFeatures # 解锁商业特性
6.2 GC 日志解读
java
# 示例 GC 日志解读
# 1. Young GC 日志
2024-01-15T10:23:45.123+0800: [GC (Allocation Failure)
[ParNew: 524288K->43520K(524288K), 0.0234567 secs]
1062400K->592320K(2097152K), 0.0245678 secs]
[Times: user=0.10 sys=0.01, real=0.02 secs]
解析:
- ParNew: 新生代收集(ParNew 收集器)
- 524288K->43520K: 收集前->收集后(新生代)
- 524288K: 新生代总大小
- 0.0234567 secs: GC 耗时
- 1062400K->592320K: 堆内存使用
- 2097152K: 堆总大小
- Times: user=用户CPU时间, sys=系统CPU时间, real=实际耗时
# 2. Full GC 日志
2024-01-15T10:25:30.456+0800: [Full GC (Allocation Failure)
[CMS: 824320K->0K(1048576K), 2.3456789 secs]
1879048K->524288K(2097152K), 2.3467890 secs]
[Times: user=8.12 sys=0.56, real=2.35 secs]
解析:
- Full GC: 整个堆收集
- CMS: 老年代使用 CMS 收集器
- 824320K->0K: 老年代回收前后
- 2.3456789 secs: CMS 收集耗时
# 3. CMS 日志
2024-01-15T10:25:30.456+0800: [GC (CMS Initial Mark)
[CMS-initial-mark: 824320K(1048576K)] 824320K]
0.0123456 secs]
2025-01-15T10:25:30.468+0800: [CMS-concurrent-mark-start]
2025-01-15T10:25:31.234+0800: [CMS-concurrent-mark-end, 0.7654321 secs]
2025-01-15T10:25:31.234+0800: [CMS-concurrent-preclean-start]
2024-01-15T10:25:31.456+0800: [CMS-concurrent-preclean-end, 0.2222222 secs]
2025-01-15T10:25:31.456+0800: [CMS-remark
[Rescan (parallel) , 0.1234567 secs]
[weak refs processing, 0.0123456 secs]
[scrub string table, 0.0034567 secs]
0.1456789 secs]
2025-01-15T10:25:31.601+0800: [CMS-concurrent-sweep-start]
2025-01-15T10:25:32.456+0800: [CMS-concurrent-sweep-end, 0.8543210 secs]
2025-01-15T10:25:32.456+0800: [CMS-concurrent-reset-start]
2025-01-15T10:25:32.567+0800: [CMS-concurrent-reset-end, 0.1111111 secs]
6.3 调优案例
案例1:CMS 调优
java
# 典型 CMS 配置
java -server \
-Xms4g -Xmx4g \
-XX:NewRatio=2 \
-XX:SurvivorRatio=8 \
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:+UseCMSCompactAtFullCollection \
-XX:CMSFullGCsBeforeCompaction=5 \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-Xloggc:gc.log \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heap.hprof \
-jar app.jar
# 解释:
# -XX:CMSInitiatingOccupancyFraction=70
# 老年代使用率70%时触发CMS(避免Promotion Failed)
# -XX:+UseCMSCompactAtFullCollection
# FullGC后整理内存,减少碎片
# -XX:CMSFullGCsBeforeCompaction=5
# 5次FullGC后进行一次整理
案例2:G1 调优
java
# 典型 G1 配置
java -server \
-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1NewSizePercent=5 \
-XX:G1MaxNewSizePercent=60 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1HeapRegionSize=4m \
-XX:G1ReservePercent=10 \
-XX:ParallelGCThreads=4 \
-XX:ConcGCThreads=2 \
-Xlog:gc*:file=gc.log:time,uptime,level,tags \
-XX:+HeapDumpOnOutOfMemoryError \
-jar app.jar
# 解释:
# -XX:MaxGCPauseMillis=200
# 目标停顿时间200ms
# -XX:InitiatingHeapOccupancyPercent=45
# 堆使用率45%时开始Mixed GC
# -XX:G1HeapRegionSize=4m
# Region大小4MB(1/2/4/8/16/32MB可选)
# -XX:G1ReservePercent=10
# 预留10%空间防止to-space overflow
案例3:ZGC 调优(JDK 11+)
java
# ZGC 配置
java -server \
-Xms16g -Xmx16g \
-XX:+UseZGC \
-XX:MaxGCPauseMillis=10 \
-XX:+ZCollectionInterval=0 \
-XX:ZProactive=false \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseTransparentHugePages \
-Xlog:gc*:file=gc.log \
-jar app.jar
第七部分:工具链
7.1 JDK 内置工具
java
# 1. jps - 查看 Java 进程
jps # 列出所有 Java 进程
jps -l # 显示完整类名/jar名
jps -v # 显示 JVM 参数
# 2. jstat - 统计信息监控
jstat -gcutil <pid> 1000 # 每秒打印 GC 统计(百分比)
jstat -gc <pid> 1000 # 每秒打印 GC 容量信息
jstat -class <pid> # 打印类加载统计
jstat -compiler <pid> # 打印 JIT 编译统计
# 输出示例:
# S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCU YGC YGCT FGC FGCT GCT
# 41984.0 41984.0 0.0 38592.0 335872.0 268288.0 629145.6 419430.4 97152.0 94034.7 12288.0 11526.3 156 8.432 5 3.456 11.888
# 3. jinfo - 查看/修改配置
jinfo <pid> # 打印所有配置
jinfo -flags <pid> # 打印所有 JVM 参数
jinfo -sysprops <pid> # 打印系统属性
jinfo -flag <name> <pid> # 打印某个参数值
jinfo -flag +<flag> <pid> # 启用参数(部分可动态修改)
jinfo -flag -<flag> <pid> # 禁用参数
# 4. jmap - 内存映射
jmap -heap <pid> # 打印堆配置和使用情况
jmap -histo <pid> # 打印对象统计(按大小排序)
jmap -histo:live <pid> # 先触发 Full GC 再统计(慎用)
jmap -dump:format=b,file=heap.hprof <pid> # 导出堆转储
jmap -clstats <pid> # 打印类加载器统计
# 5. jstack - 线程堆栈
jstack <pid> # 打印线程堆栈
jstack -l <pid> # 打印锁信息
jstack -F <pid> # 强制打印(用于无响应情况)
# 6. jcmd - 多功能命令(JDK 7+)
jcmd <pid> VM.flags # 打印 JVM 参数
jcmd <pid> GC.heap_info # 打印堆信息
jcmd <pid> GC.class_histogram # 对象统计
jcmd <pid> Thread.print # 打印线程堆栈
jcmd <pid> VM.native_memory跟踪 # 本地内存跟踪
7.2 可视化工具
VisualVM
java
# 启动 VisualVM
jvisualvm
# 功能:
# 1. CPU 性能分析
# 2. 内存分析(堆dump、对象统计)
# 3. 线程分析
# 4. GC 分析
# 5. 快照对比
JProfiler(商业)
XML
# 启动 VisualVM
jvisualvm
# 功能:
# 1. CPU 性能分析
# 2. 内存分析(堆dump、对象统计)
# 3. 线程分析
# 4. GC 分析
# 5. 快照对比
Arthas(阿里开源)
java
# 1. 下载安装
curl -O https://arthas.gitee.io/arthas-boot.jar
java -jar arthas-boot.jar
# 2. 常用命令
# 查看类信息
sc -d com.example.User
# 反编译类
jad com.example.User
# 查看方法调用
watch com.example.User methodName '{params, returnObj}'
# 追踪方法调用
trace com.example.User methodName
# 查看方法栈
stack com.example.User methodName
# 监控方法调用
monitor -c 5 com.example.User methodName
# 生成火焰图
profiler start
profiler stop --format html
# 线程分析
thread
thread -b # 死锁检测
# 内存分析
heapdump /tmp/heap.hprof
# GC 分析
dashboard # 实时面板
MAT(Memory Analyzer Tool)
java
# 安装 MAT
# 下载:https:// eclipse.org/mat/downloads.php
# 导入堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>
# 用 MAT 分析
# 1. Dominator Tree - 找出最大的对象树
# 2. Histogram - 对象直方图
# 3. Leak Suspects - 内存泄漏怀疑点
# 4. Top Consumers - 最大的对象
7.3 GC 日志分析工具
java
# GCViewer
# https://github.com/chewiebug/GCViewer
java -jar gcviewer.jar gc.log
# GCEasy
# https://gceasy.io
# 在线分析 GC 日志
# GCPlot
# https://gcplot.com
# 开源 GC 日志分析
第八部分:OOM 全场景排查
8.1 OOM 类型与原因
java
/**
* OOM 场景分类
*/
public class OOMScenarios {
// ============ 1. 堆内存溢出 (Heap OOM) ============
public void heapOOM() {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 1MB
}
}
// 原因:对象无限创建、内存泄漏、堆太小
// 解决:增大堆、优化代码、检查泄漏
// ============ 2. 元空间溢出 (Metaspace OOM) ============
// 原因:类加载过多(CGLib动态代理、OSGi、JSP编译)
// 解决:增大元空间、优化类加载逻辑
// ============ 3. 栈溢出 (StackOverflowError) ============
public void stackOverflow() {
stackOverflow(); // 无限递归
}
// 解决:修复递归逻辑、增大栈 (-Xss)
// ============ 4. 栈内存耗尽 (Unable to create new native thread) ============
// 原因:线程数过多
// 解决:减少线程数、优化线程创建
// ============ 5. 直接内存溢出 (DirectBuffer OOM) ============
public void directMemoryOOM() {
ByteBuffer buffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
}
// 原因:NIO 使用过多
// 解决:增大直接内存、检查 ByteBuffer 使用
}
8.2 OOM 排查实战
步骤1:配置 OOM 时自动导出堆
java
# JVM 参数
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heap.hprof \
-XX:+ExitOnOutOfMemoryError \
-XX:+CrashOnOutOfMemoryError
步骤2:使用 MAT 分析
java
# 1. 下载 MAT: https://www.eclipse.org/mat/downloads.php
# 2. 打开堆转储文件
./MemoryAnalyzer heap.hprof
# 3. 关键分析功能:
# Histogram(直方图)
# - 按类名分组,查看对象数量和内存占用
# - 找出占用最大的类
# Dominator Tree(支配树)
# - 找出 GC Root 到对象的路径
# - 快速定位内存泄漏
# Leak Suspects(泄漏怀疑点)
# - MAT 自动分析可能的泄漏点
# Top Consumers(最大消费者)
# - 列出占用内存最大的对象
步骤3:使用 jmap + jhat 分析
java
# 1. 导出堆
jmap -dump:format=b,file=heap.hprof <pid>
# 2. 使用 jhat 分析(内置工具,简单场景够用)
jhat heap.hprof
# 3. 浏览器访问 http://localhost:7000
# 4. 使用 OQL 查询
# select x from com.example.User x where x.age > 30
步骤4:使用 Arthas 分析
java
# 1. 启动 Arthas
java -jar arthas-boot.jar
# 2. 生成堆转储
heapdump /tmp/heap.hprof
# 3. 分析大对象
sc -d com.example.HeavyObject
# 4. 查看对象创建
watch com.example.User <init> '{params, throwExp}' -x 3
8.3 常见 OOM 场景与解决方案
| OOM 场景 | 原因 | 解决方案 |
|---|---|---|
| 集合类未清理 | 集合只增不减 | 及时清理、使用 WeakHashMap |
| 静态集合持有引用 | 生命周期过长 | 避免 static 持有大对象 |
| 单例模式持有 Context | Activity/Context 泄漏 | 使用 Application Context |
| 监听器未注销 | 未移除监听器 | onDestroy 中注销 |
| Handler 内存泄漏 | 非静态内部类持有外部引用 | 使用静态内部类 + WeakReference |
| ThreadLocal 泄漏 | 线程池复用未清理 | 手动 remove() |
| 资源未关闭 | Stream、Connection 未关闭 | try-with-resources |
| 静态集合存储缓存 | 缓存无限增长 | 使用 LRU 缓存或 WeakReference |
| CGLib 代理过多 | 类加载器泄漏 | 限制动态代理使用 |
| 大量字符串拼接 | StringBuilder 泄漏 | 及时 clear() 或限制大小 |
第九部分:全链路性能监控
9.1 APM 工具选型
| 工具 | 特点 | 开源 | 商业 |
|---|---|---|---|
| SkyWalking | 链路追踪、性能指标 | ✅ | - |
| Pinpoint | 韩国开源,字节码注入 | ✅ | - |
| Zipkin | Twitter 开源,轻量 | ✅ | - |
| Jaeger | CNCF 项目,Go 实现 | ✅ | - |
| CAT | 大众点评开源 | ✅ | - |
| New Relic | 功能全面 | - | ✅ |
| Dynatrace | AI 驱动 | - | ✅ |
| AppDynamics | 应用性能 | - | ✅ |
9.2 SkyWalking 实战
java
# docker-compose.yml
version: '3'
services:
oap:
image: apache/skywalking-oap-server:9.5.0
ports:
- "11800:11800" # gRPC
- "12800:12800" # HTTP
environment:
SW_STORAGE: elasticsearch
SW_STORAGE_ES_HOSTS: elasticsearch:9200
ui:
image: apache/skywalking-ui:9.5.0
ports:
- "8080:8080"
depends_on:
- oap
environment:
SW_OAP_ADDRESS: oap:12800
elasticsearch:
image: elasticsearch:8.9.0
ports:
- "9200:9200"
9.3 Arthas 高级用法
java
# 1. 火焰图 - 定位 CPU 热点
profiler start --duration 30
profiler stop --format html --file /tmp/flame.html
# 2. 方法执行耗时
trace com.example.UserService * '{methodName, #cost > 100}'
# 3. 监控方法调用
monitor -c 5 com.example.UserService addUser
# 4. 查看热点方法
jfr record --duration=60s
# 或使用 async-profiler
# 5. 类重编译(热部署)
retransform com.example.UserService
9.4 全链路排查流程
java
问题发现 → 指标分析 → 链路追踪 → 定位根因 → 优化验证
↓ ↓ ↓ ↓ ↓
监控告警 Grafana SkyWalking Arthas 验证优化
第十部分:完整的 JVM 深度学习体系!内容涵盖
✅ 运行时数据区 - 堆、栈、方法区完整解析
✅ 类加载子系统 - 5阶段流程、双亲委派、自定义加载器
✅ 垃圾回收机制 - 算法、收集器、分代策略
✅ 字节码与执行引擎 - 结构、指令、JIT优化
✅ 实战调优 - 参数配置、案例分析
✅ 工具链 - JDK工具、可视化工具、Arthas
✅ 性能监控 - 全链路监控方案
✅ OOM排查 - 全场景分析