JVM从基础到架构实战

1. 概述

1.1 JVM的重要性

Java虚拟机(JVM)是Java平台的核心,负责执行Java字节码,管理内存,进行垃圾回收等。深入理解JVM对于Java开发人员至关重要,可以帮助我们编写高性能代码,进行性能调优,解决生产环境问题。

JVM的价值

  • 跨平台:一次编译,到处运行
  • 内存管理:自动内存管理和垃圾回收
  • 性能优化:通过JVM调优提升应用性能
  • 问题诊断:通过JVM工具诊断和解决生产问题

1.2 JVM发展历程

Java版本 JVM版本 主要特性
Java 1.0 HotSpot 1.0 初始版本
Java 1.2 HotSpot 1.2 性能优化
Java 1.4 HotSpot 1.4 性能大幅提升
Java 1.5 HotSpot 1.5 并发性能优化
Java 1.6 HotSpot 1.6 G1 GC预览
Java 1.7 HotSpot 1.7 G1 GC正式版
Java 1.8 HotSpot 1.8 移除永久代,Metaspace
Java 9+ HotSpot 9+ 模块化、ZGC、Shenandoah GC

1.3 主流JVM实现

JVM实现 开发组织 特点
HotSpot Oracle/Sun 最广泛使用,性能优秀
OpenJ9 IBM/Eclipse 低内存占用,启动快
GraalVM Oracle 高性能,多语言支持
Zing Azul 低延迟GC,商业产品

1.4 本文内容结构

本文将从以下几个方面全面解析JVM:

  1. JVM基础:JVM架构、内存模型
  2. 类加载机制:类加载过程、双亲委派模型
  3. 内存管理:堆内存、方法区、栈内存
  4. 垃圾回收:GC算法、GC收集器
  5. 字节码执行:字节码结构、执行引擎
  6. 性能调优:JVM参数调优、GC调优
  7. 故障排查:内存泄漏、GC问题诊断
  8. 架构实战:生产环境JVM配置

2. JVM基础

2.1 JVM架构

2.1.1 JVM整体架构

JVM架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────┐
│ Java应用程序 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ Class文件 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 类加载子系统 │
│ (Class Loader Subsystem) │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 运行时数据区 │
│ (Runtime Data Areas) │
│ ┌──────────┐ ┌──────────┐ │
│ │ 方法区 │ │ 堆 │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ PC寄存器│ │ Java栈 │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ │
│ │ 本地方法栈│ │
│ └──────────┘ │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 执行引擎 │
│ (Execution Engine) │
│ ┌──────────┐ ┌──────────┐ │
│ │ 解释器 │ │ JIT编译器│ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 本地方法接口 │
│ (Native Method Interface) │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ 本地方法库 │
│ (Native Method Libraries) │
└─────────────────────────────────────────┘

2.1.2 JVM核心组件

1. 类加载子系统(Class Loader Subsystem)

  • 负责加载Class文件
  • 将字节码转换为JVM内部数据结构

2. 运行时数据区(Runtime Data Areas)

  • 方法区(Method Area)
  • 堆(Heap)
  • 程序计数器(PC Register)
  • Java虚拟机栈(Java Virtual Machine Stack)
  • 本地方法栈(Native Method Stack)

3. 执行引擎(Execution Engine)

  • 解释器(Interpreter)
  • JIT编译器(Just-In-Time Compiler)
  • 垃圾回收器(Garbage Collector)

4. 本地方法接口(Native Method Interface)

  • 调用本地方法

2.2 JVM内存模型

2.2.1 运行时数据区详解

1. 程序计数器(PC Register)

1
2
3
4
// 程序计数器特点
- 线程私有
- 记录当前线程执行的字节码指令地址
- 唯一不会发生OutOfMemoryError的区域

2. Java虚拟机栈(Java Virtual Machine Stack)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 栈结构
┌─────────────┐
│ 栈帧1 │ ← 当前方法
├─────────────┤
│ 栈帧2
├─────────────┤
│ 栈帧3
└─────────────┘

// 栈帧结构
┌─────────────────┐
│ 局部变量表 │
├─────────────────┤
│ 操作数栈 │
├─────────────────┤
│ 动态链接 │
├─────────────────┤
│ 方法返回地址 │
└─────────────────┘

栈的特点

  • 线程私有
  • 每个方法对应一个栈帧
  • 可能出现StackOverflowError和OutOfMemoryError

3. 本地方法栈(Native Method Stack)

  • 为本地方法(Native Method)服务
  • 与Java虚拟机栈类似

4. 堆(Heap)

1
2
3
4
5
6
7
8
9
10
11
12
// 堆内存结构(Java 8+)
┌─────────────────────────────┐
│ 堆 (Heap) │
├─────────────────────────────┤
│ 新生代 (Young Generation) │
│ ┌──────────┬──────────┐ │
│ │ Eden │ Survivor │ │
│ │ │ S0/S1 │ │
│ └──────────┴──────────┘ │
├─────────────────────────────┤
│ 老年代 (Old Generation) │
└─────────────────────────────┘

堆的特点

  • 线程共享
  • 存储对象实例
  • 垃圾回收的主要区域
  • 可能出现OutOfMemoryError

5. 方法区(Method Area)

Java 8之前

  • 永久代(PermGen)
  • 存储类信息、常量、静态变量

**Java 8+**:

  • 元空间(Metaspace)
  • 使用本地内存,不再受JVM堆内存限制

方法区存储内容

  • 类信息(Class信息)
  • 运行时常量池
  • 静态变量
  • JIT编译后的代码

2.2.2 内存参数配置

堆内存参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 初始堆内存
-Xms2g

# 最大堆内存
-Xmx4g

# 新生代大小
-Xmn1g

# 或使用比例
-XX:NewRatio=2 # 新生代:老年代 = 1:2

# Survivor区比例
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1:1

方法区参数(Java 8之前)

1
2
3
4
5
# 永久代初始大小
-XX:PermSize=256m

# 永久代最大大小
-XX:MaxPermSize=512m

元空间参数(Java 8+)

1
2
3
4
5
# 元空间初始大小
-XX:MetaspaceSize=256m

# 元空间最大大小
-XX:MaxMetaspaceSize=512m

栈内存参数

1
2
3
4
# 每个线程的栈大小
-Xss1m
# 或
-XX:ThreadStackSize=1024

3. 类加载机制

3.1 类加载过程

3.1.1 类加载的5个阶段

1. 加载(Loading)

  • 通过类的全限定名获取类的二进制字节流
  • 将字节流转换为方法区的运行时数据结构
  • 在内存中生成Class对象

2. 验证(Verification)

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

3. 准备(Preparation)

  • 为类变量分配内存
  • 设置默认初始值(零值)

4. 解析(Resolution)

  • 将常量池中的符号引用转换为直接引用

5. 初始化(Initialization)

  • 执行类构造器<clinit>()方法
  • 初始化类变量和静态代码块

3.1.2 类加载示例

1
2
3
4
5
6
7
8
9
10
11
12
public class ClassLoadingExample {
static {
System.out.println("静态代码块执行");
}

private static int value = 10;

public static void main(String[] args) {
System.out.println("main方法执行");
System.out.println("value = " + value);
}
}

执行顺序

  1. 加载Class文件
  2. 验证字节码
  3. 准备:value = 0(默认值)
  4. 解析符号引用
  5. 初始化:执行静态代码块,value = 10

3.2 类加载器

3.2.1 类加载器层次结构

双亲委派模型(Parent Delegation Model)

1
2
3
4
5
6
7
Bootstrap ClassLoader (启动类加载器)

Extension ClassLoader (扩展类加载器)

Application ClassLoader (应用程序类加载器)

Custom ClassLoader (自定义类加载器)

类加载器说明

类加载器 加载路径 说明
Bootstrap $JAVA_HOME/jre/lib C++实现,JVM一部分
Extension $JAVA_HOME/jre/lib/ext Java实现
Application $CLASSPATH 系统类加载器
Custom 自定义 用户自定义

3.2.2 双亲委派模型

工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委托父加载器加载
c = parent.loadClass(name, false);
} else {
// 3. 委托Bootstrap加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 4. 父加载器无法加载,自己加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

双亲委派的优势

  • 避免类重复加载
  • 保证核心类安全(防止核心类被替换)
  • 保证类加载的唯一性

3.2.3 自定义类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class CustomClassLoader extends ClassLoader {
private String classPath;

public CustomClassLoader(String classPath) {
this.classPath = classPath;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}

private byte[] loadClassData(String className) {
String fileName = classPath + className.replace('.', '/') + ".class";
try (FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}

4. 内存管理

4.1 堆内存管理

4.1.1 堆内存结构

Java 8之前的堆结构

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────┐
│ 堆 (Heap) │
├─────────────────────────────┤
│ 新生代 (Young) │
│ ┌──────────┬──────────┐ │
│ │ Eden │ Survivor │ │
│ │ │ S0/S1 │ │
│ └──────────┴──────────┘ │
├─────────────────────────────┤
│ 老年代 (Old) │
├─────────────────────────────┤
│ 永久代 (PermGen) │
└─────────────────────────────┘

Java 8+的堆结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────┐
│ 堆 (Heap) │
├─────────────────────────────┤
│ 新生代 (Young) │
│ ┌──────────┬──────────┐ │
│ │ Eden │ Survivor │ │
│ │ │ S0/S1 │ │
│ └──────────┴──────────┘ │
├─────────────────────────────┤
│ 老年代 (Old) │
└─────────────────────────────┘

┌─────────────────────────────┐
│ 元空间 (Metaspace) │
│ (本地内存) │
└─────────────────────────────┘

4.1.2 对象分配过程

对象分配流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
新对象创建

Eden区是否有空间?
↓ 是
分配到Eden区

Eden区满 → Minor GC

存活对象 → Survivor区

年龄达到阈值(默认15)?
↓ 是
晋升到老年代
↓ 否
继续在Survivor区

老年代满 → Full GC

对象分配代码示例

1
2
3
4
5
6
7
8
9
public class ObjectAllocation {
public static void main(String[] args) {
// 对象分配在Eden区
byte[] obj1 = new byte[1024 * 1024]; // 1MB

// 大对象直接进入老年代(如果超过-XX:PretenureSizeThreshold)
byte[] obj2 = new byte[10 * 1024 * 1024]; // 10MB
}
}

4.2 栈内存管理

4.2.1 栈帧结构

栈帧详细结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────┐
│ 栈帧 (Stack Frame) │
├─────────────────────────────┤
│ 局部变量表 (Local Variables)│
│ ┌────┬────┬────┬────┐ │
│ │slot│slot│slot│slot│ │
│ └────┴────┴────┴────┘ │
├─────────────────────────────┤
│ 操作数栈 (Operand Stack) │
│ ┌────┐ │
│ │ │ │
│ └────┘ │
├─────────────────────────────┤
│ 动态链接 (Dynamic Linking) │
├─────────────────────────────┤
│ 方法返回地址 (Return Addr) │
└─────────────────────────────┘

局部变量表示例

1
2
3
4
public void method(int a, int b) {
int c = a + b; // 局部变量
String str = "hello"; // 局部变量
}

局部变量表slot分配

  • slot 0: this(实例方法)
  • slot 1: 参数a
  • slot 2: 参数b
  • slot 3: 局部变量c
  • slot 4: 局部变量str(引用)

5. 垃圾回收

5.1 垃圾回收基础

5.1.1 什么是垃圾

垃圾的定义

  • 不再被引用的对象
  • 循环引用的对象(如果没有外部引用)

判断对象是否为垃圾的方法

1. 引用计数法

  • 每个对象有一个引用计数器
  • 引用+1,引用失效-1
  • 计数器为0的对象是垃圾
  • 缺点:无法解决循环引用问题

2. 可达性分析(Java采用)

  • 从GC Roots开始向下搜索
  • 能够到达的对象是存活对象
  • 不能到达的对象是垃圾

GC Roots包括

  • 虚拟机栈中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中引用的对象
  • 同步锁持有的对象

5.1.2 引用类型

Java 4种引用类型

1. 强引用(Strong Reference)

1
Object obj = new Object();  // 强引用
  • 只要强引用存在,对象不会被回收

2. 软引用(Soft Reference)

1
2
3
4
import java.lang.ref.SoftReference;

SoftReference<Object> softRef = new SoftReference<>(new Object());
Object obj = softRef.get(); // 可能为null
  • 内存不足时回收
  • 适合做缓存

3. 弱引用(Weak Reference)

1
2
3
4
import java.lang.ref.WeakReference;

WeakReference<Object> weakRef = new WeakReference<>(new Object());
Object obj = weakRef.get(); // 可能为null
  • 下次GC时回收
  • 适合做缓存(WeakHashMap)

4. 虚引用(Phantom Reference)

1
2
3
4
5
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
  • 无法通过虚引用获取对象
  • 对象回收时收到通知
  • 用于跟踪对象回收

5.2 垃圾回收算法

5.2.1 标记-清除算法(Mark-Sweep)

算法过程

  1. 标记:标记所有需要回收的对象
  2. 清除:回收被标记的对象

缺点

  • 效率低(标记和清除效率都不高)
  • 产生内存碎片

5.2.2 复制算法(Copying)

算法过程

  1. 将内存分为两块
  2. 只使用其中一块
  3. 当这一块用完,将存活对象复制到另一块
  4. 清空当前块

优点

  • 效率高
  • 无内存碎片

缺点

  • 内存利用率低(只能使用一半)

应用:新生代(Eden和Survivor)

5.2.3 标记-整理算法(Mark-Compact)

算法过程

  1. 标记:标记所有需要回收的对象
  2. 整理:将存活对象向一端移动
  3. 清理:清理边界外的内存

优点

  • 无内存碎片
  • 内存利用率高

缺点

  • 效率较低(需要移动对象)

应用:老年代

5.2.4 分代收集算法(Generational Collection)

算法思想

  • 根据对象存活周期将堆分为新生代和老年代
  • 新生代使用复制算法
  • 老年代使用标记-清除或标记-整理算法

分代收集流程

1
2
3
4
5
6
7
8
9
新对象 → Eden区

Eden满 → Minor GC

存活对象 → Survivor区

年龄达到阈值 → 老年代

老年代满 → Full GC

5.3 垃圾收集器

5.3.1 串行收集器(Serial)

特点

  • 单线程收集
  • 适合小应用
  • 客户端模式默认收集器

使用参数

1
2
# 新生代使用Serial GC
-XX:+UseSerialGC

5.3.2 并行收集器(Parallel)

特点

  • 多线程收集
  • 适合吞吐量优先的应用
  • 服务器模式默认收集器(Java 8之前)

使用参数

1
2
3
4
5
# 使用Parallel GC
-XX:+UseParallelGC

# 设置并行线程数
-XX:ParallelGCThreads=4

5.3.3 并发标记清除收集器(CMS)

特点

  • 并发收集,减少停顿时间
  • 适合响应时间敏感的应用
  • Java 9标记为废弃,Java 14移除

使用参数

1
2
3
4
5
# 使用CMS GC
-XX:+UseConcMarkSweepGC

# CMS并发线程数
-XX:ConcGCThreads=2

5.3.4 G1收集器(Garbage First)

特点

  • 低延迟,适合大堆内存
  • 将堆分为多个Region
  • Java 9默认收集器

使用参数

1
2
3
4
5
6
7
8
# 使用G1 GC
-XX:+UseG1GC

# 设置最大GC停顿时间
-XX:MaxGCPauseMillis=200

# 设置Region大小
-XX:G1HeapRegionSize=16m

G1堆结构

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────┐
│ G1堆 (多个Region) │
├─────────────────────────────┤
│ ┌────┐┌────┐┌────┐┌────┐ │
│ │Eden││Surv││ Old││Hum │ │
│ └────┘└────┘└────┘└────┘ │
│ ┌────┐┌────┐┌────┐┌────┐ │
│ │Eden││Surv││ Old││Hum │ │
│ └────┘└────┘└────┘└────┘ │
└─────────────────────────────┘

5.3.5 ZGC收集器

特点

  • 超低延迟(<10ms)
  • 适合超大堆内存(TB级别)
  • Java 11引入(实验性),Java 15正式版

使用参数

1
2
3
4
5
# 使用ZGC(Java 11+)
-XX:+UseZGC

# 设置最大堆内存
-Xmx16g

5.3.6 Shenandoah收集器

特点

  • 低延迟GC
  • 并发回收
  • OpenJDK提供

使用参数

1
2
# 使用Shenandoah GC
-XX:+UseShenandoahGC

5.4 GC日志分析

5.4.1 GC日志配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 打印GC日志
-XX:+PrintGC

# 打印详细GC信息
-XX:+PrintGCDetails

# 打印GC时间戳
-XX:+PrintGCDateStamps

# GC日志文件
-Xloggc:/path/to/gc.log

# Java 9+ 统一日志
-Xlog:gc*:file=/path/to/gc.log:time,tags,level

5.4.2 GC日志格式

Parallel GC日志

1
[GC (Allocation Failure) [PSYoungGen: 8192K->1024K(9216K)] 8192K->2048K(10240K), 0.0123456 secs]

G1 GC日志

1
2
3
4
5
6
7
8
9
10
11
12
[GC pause (G1 Evacuation Pause) (young), 0.0123456 secs]
[Parallel Time: 10.0 ms, GC Workers: 4]
[GC Worker Start (ms): Min: 100.0, Avg: 100.1, Max: 100.2]
[Ext Root Scanning (ms): Min: 1.0, Avg: 1.1, Max: 1.2]
[Update RS (ms): Min: 0.0, Avg: 0.1, Max: 0.2]
[Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.2]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0]
[Object Copy (ms): Min: 8.0, Avg: 8.1, Max: 8.2]
[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2]
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.2]
[GC Worker Total (ms): Min: 9.0, Avg: 9.1, Max: 9.2]
[GC Worker End (ms): Min: 109.0, Avg: 109.2, Max: 109.4]

6. 字节码执行

6.1 字节码基础

6.1.1 Class文件结构

Class文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────┐
│ Magic Number │ (0xCAFEBABE)
├─────────────────┤
│ Minor Version │
├─────────────────┤
│ Major Version │
├─────────────────┤
│ Constant Pool │ (常量池)
├─────────────────┤
│ Access Flags │ (访问标志)
├─────────────────┤
│ This Class │ (当前类)
├─────────────────┤
│ Super Class │ (父类)
├─────────────────┤
│ Interfaces │ (接口)
├─────────────────┤
│ Fields │ (字段)
├─────────────────┤
│ Methods │ (方法)
├─────────────────┤
│ Attributes │ (属性)
└─────────────────┘

6.1.2 查看字节码

使用javap命令

1
2
3
4
5
# 查看字节码
javap -c MyClass.class

# 查看详细信息
javap -verbose MyClass.class

示例代码

1
2
3
4
5
public class BytecodeExample {
public int add(int a, int b) {
return a + b;
}
}

字节码输出

1
2
3
4
5
6
public int add(int, int);
Code:
0: iload_1 // 加载局部变量1(参数a)
1: iload_2 // 加载局部变量2(参数b)
2: iadd // 执行加法
3: ireturn // 返回结果

6.2 执行引擎

6.2.1 解释执行

解释器(Interpreter)

  • 逐条解释执行字节码
  • 启动快,执行慢

6.2.2 编译执行

JIT编译器(Just-In-Time Compiler)

  • 将热点代码编译为本地代码
  • 启动慢,执行快

热点代码检测

  • 方法调用计数器
  • 回边计数器

6.2.3 混合模式

JVM默认模式

  • 解释器 + JIT编译器
  • 启动时使用解释器
  • 热点代码使用JIT编译

JIT编译器类型

C1编译器(Client Compiler)

  • 编译速度快
  • 优化程度低
  • 适合客户端应用

C2编译器(Server Compiler)

  • 编译速度慢
  • 优化程度高
  • 适合服务器应用

分层编译(Tiered Compilation)

  • Java 7引入
  • 结合C1和C2的优势

7. 性能调优

7.1 JVM参数调优

7.1.1 堆内存调优

基础参数

1
2
3
4
5
6
7
8
9
10
11
# 初始堆内存(建议与最大堆内存相同)
-Xms4g

# 最大堆内存
-Xmx4g

# 新生代大小
-Xmn2g

# 或使用比例
-XX:NewRatio=2 # 新生代:老年代 = 1:2

调优建议

  • Xms和Xmx设置为相同值,避免动态调整
  • 堆内存 = 物理内存的50%-70%
  • 新生代 = 堆内存的1/3-1/4

7.1.2 GC调优

G1 GC调优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用G1 GC
-XX:+UseG1GC

# 最大GC停顿时间(毫秒)
-XX:MaxGCPauseMillis=200

# Region大小
-XX:G1HeapRegionSize=16m

# 并发GC线程数
-XX:ConcGCThreads=4

# 并行GC线程数
-XX:ParallelGCThreads=8

Parallel GC调优

1
2
3
4
5
6
7
8
# 使用Parallel GC
-XX:+UseParallelGC

# 并行GC线程数
-XX:ParallelGCThreads=8

# GC自适应调整
-XX:+UseAdaptiveSizePolicy

7.1.3 方法区调优

Java 8+(Metaspace)

1
2
3
4
5
# 元空间初始大小
-XX:MetaspaceSize=256m

# 元空间最大大小
-XX:MaxMetaspaceSize=512m

7.2 GC调优实战

7.2.1 调优目标

吞吐量优先

  • 适合批处理应用
  • 使用Parallel GC

延迟优先

  • 适合Web应用
  • 使用G1 GC或ZGC

7.2.2 调优步骤

1. 收集GC日志

1
2
3
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log

2. 分析GC日志

1
2
# 使用GCViewer分析
# 或使用在线工具:gceasy.io

3. 调整参数

1
2
3
4
# 根据分析结果调整
# - 如果Full GC频繁,增加堆内存
# - 如果Minor GC频繁,调整新生代大小
# - 如果GC停顿时间长,使用G1 GC

7.3 性能监控工具

7.3.1 jstat

1
2
3
4
5
# 查看GC统计
jstat -gcutil <pid> 1000 10

# 查看堆内存
jstat -gc <pid> 1000 10

7.3.2 jmap

1
2
3
4
5
# 查看堆内存使用
jmap -heap <pid>

# 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

7.3.3 jstack

1
2
3
4
5
# 生成线程转储
jstack <pid> > thread.txt

# 检测死锁
jstack <pid> | grep -i deadlock

7.3.4 VisualVM

功能

  • 实时监控JVM
  • 分析堆转储
  • 分析线程转储
  • 性能分析

8. 故障排查

8.1 内存泄漏

8.1.1 内存泄漏诊断

症状

  • 堆内存持续增长
  • Full GC频繁
  • 应用响应变慢

诊断步骤

1
2
3
4
5
6
7
8
9
# 1. 查看堆内存使用
jmap -heap <pid>

# 2. 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

# 3. 使用MAT分析堆转储
# 查找占用内存最大的对象
# 分析对象引用关系

8.1.2 常见内存泄漏原因

1. 集合类泄漏

1
2
3
4
5
6
7
8
// 错误示例
public class MemoryLeak {
private static List<Object> list = new ArrayList<>();

public void add(Object obj) {
list.add(obj); // 对象永远不会被移除
}
}

2. 监听器泄漏

1
2
3
4
5
6
7
8
// 错误示例
public class ListenerLeak {
public void register() {
someObject.addListener(new Listener() {
// 监听器持有外部引用,无法被回收
});
}
}

3. 内部类持有外部类引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误示例
public class Outer {
private Object data;

public void method() {
new Thread(new Runnable() {
@Override
public void run() {
// 内部类持有Outer引用
System.out.println(data);
}
}).start();
}
}

8.2 GC问题诊断

8.2.1 Full GC频繁

原因

  • 堆内存不足
  • 老年代空间不足
  • 大对象过多

解决方案

1
2
3
4
5
6
7
8
# 增加堆内存
-Xmx8g

# 调整新生代大小
-Xmn4g

# 调整大对象阈值
-XX:PretenureSizeThreshold=2m

8.2.2 GC停顿时间长

原因

  • 堆内存过大
  • GC算法不适合

解决方案

1
2
3
4
5
6
# 使用G1 GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

# 或使用ZGC(Java 11+)
-XX:+UseZGC

8.3 CPU占用高

8.3.1 诊断步骤

1
2
3
4
5
6
7
8
9
# 1. 查看CPU使用情况
top -p <pid>

# 2. 生成线程转储
jstack <pid> > thread.txt

# 3. 分析线程状态
# 查找RUNNABLE状态的线程
# 分析线程堆栈

8.3.2 常见原因

1. 死循环

1
2
3
4
// 错误示例
while (true) {
// 无条件的循环
}

2. 频繁GC

1
2
# 查看GC情况
jstat -gcutil <pid> 1000 10

3. 锁竞争

1
2
# 查看线程转储
jstack <pid> | grep -i "locked"

9. 架构实战

9.1 生产环境JVM配置

9.1.1 推荐配置模板

Web应用(G1 GC)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 堆内存
-Xms4g
-Xmx4g

# G1 GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m

# GC日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc.log

# 元空间
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

# 其他
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heap_dump.hprof

批处理应用(Parallel GC)

1
2
3
4
5
6
7
8
9
10
11
# 堆内存
-Xms8g
-Xmx8g

# Parallel GC
-XX:+UseParallelGC
-XX:ParallelGCThreads=8

# GC日志
-XX:+PrintGCDetails
-Xloggc:/var/log/gc.log

9.1.2 不同场景配置

高并发Web应用

1
2
3
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:InitiatingHeapOccupancyPercent=45

大数据处理

1
2
3
-XX:+UseG1GC
-XX:MaxGCPauseMillis=500
-Xmx32g

低延迟应用

1
2
3
# Java 11+
-XX:+UseZGC
-Xmx16g

9.2 JVM监控方案

9.2.1 监控指标

关键指标

  • 堆内存使用率
  • GC频率和耗时
  • 线程数量
  • CPU使用率

9.2.2 监控工具

1. Prometheus + JMX Exporter

1
2
3
# 启动JMX Exporter
java -javaagent:jmx_prometheus_javaagent.jar=1234:config.yml \
-jar application.jar

2. JVM监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
PID=$1

echo "=== JVM Monitor ==="
echo "Heap Memory:"
jmap -heap $PID | grep -A 10 "Heap Usage"

echo "GC Statistics:"
jstat -gcutil $PID 1000 5

echo "Threads:"
jstack $PID | grep "java.lang.Thread.State" | sort | uniq -c

10. 总结

10.1 核心要点

  1. JVM架构:类加载、运行时数据区、执行引擎
  2. 内存管理:堆、栈、方法区的管理
  3. 垃圾回收:GC算法、GC收集器、GC调优
  4. 性能调优:JVM参数调优、GC调优
  5. 故障排查:内存泄漏、GC问题诊断

10.2 架构师建议

  1. JVM选择

    • 生产环境:HotSpot(稳定)
    • 低延迟:ZGC或Shenandoah
    • 大堆内存:G1 GC
  2. 参数调优

    • 根据应用特点选择GC
    • 合理设置堆内存大小
    • 监控GC日志,持续优化
  3. 监控告警

    • 监控堆内存使用率
    • 监控GC频率和耗时
    • 设置合理的告警阈值

10.3 最佳实践

  1. 标准化:统一JVM配置标准
  2. 监控化:实时监控JVM状态
  3. 文档化:记录调优过程和结果
  4. 持续优化:根据监控数据持续优化

相关文章