JVM原理

1. 概述

1.1 JVM的重要性

Java虚拟机(JVM)是Java程序运行的核心,理解JVM原理对于Java开发者至关重要,能够帮助我们编写高性能代码、进行性能调优、解决内存问题。

本文内容

  • JVM架构:JVM整体架构和组件
  • 内存模型:堆、栈、方法区等内存区域
  • 垃圾回收:GC算法和垃圾回收器
  • 类加载机制:类加载过程和双亲委派模型
  • 字节码执行:字节码指令和执行引擎
  • 性能调优:JVM性能调优实战

1.2 本文内容结构

本文将从以下几个方面深入探讨JVM原理:

  1. JVM架构:JVM整体架构和运行时数据区
  2. 内存模型:堆、栈、方法区详解
  3. 垃圾回收:GC算法和垃圾回收器
  4. 类加载机制:类加载过程和双亲委派
  5. 字节码执行:字节码指令和执行引擎
  6. 性能调优: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
┌─────────────────────────────────────┐
│ Java应用程序 │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ Java API (JDK) │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ JVM │
│ ┌──────────┐ ┌──────────┐ │
│ │ 类加载器 │ │执行引擎 │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │运行时数据区│ │本地方法库 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ 操作系统 (OS) │
└─────────────────────────────────────┘

JVM核心组件

  1. 类加载器(ClassLoader):加载类文件
  2. 运行时数据区(Runtime Data Areas):内存管理
  3. 执行引擎(Execution Engine):执行字节码
  4. 本地方法接口(JNI):调用本地方法

2.2 运行时数据区

2.2.1 数据区划分

运行时数据区

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
┌─────────────────────────────────────┐
│ 运行时数据区 │
│ ┌──────────────────────────────┐ │
│ │ 方法区(Method Area) │ │
│ │ - 类信息 │ │
│ │ - 常量池 │ │
│ │ - 静态变量 │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 堆(Heap) │ │
│ │ - 新生代(Eden + Survivor) │ │
│ │ - 老年代(Old Generation) │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ Java栈(Java Stack) │ │
│ │ - 局部变量表 │ │
│ │ - 操作数栈 │ │
│ │ - 方法返回地址 │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 程序计数器(PC Register) │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 本地方法栈(Native Stack) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘

3. 内存模型

3.1 堆内存

3.1.1 堆内存结构

堆内存:存储对象实例,是GC主要工作区域。

堆内存划分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HeapMemoryExample {

// 对象在堆中分配
public void heapAllocation() {
// 对象在堆中分配内存
Object obj = new Object();

// 数组在堆中分配
int[] array = new int[1000];

// 字符串在堆中分配(字符串常量池在方法区)
String str = new String("Hello");
}
}

堆内存结构

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

堆内存参数

1
2
3
4
5
6
7
8
# JVM堆内存参数
-Xms2g # 初始堆大小(-X minimum size)
-Xmx4g # 最大堆大小(-X maximum size)
-Xmn1g # 新生代大小(-X new generation)

# 比例设置
-XX:NewRatio=2 # 老年代:新生代 = 2:1
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1

3.2 栈内存

3.2.1 栈帧结构

Java栈:每个线程有独立的栈,存储局部变量、方法参数、返回地址等。

栈帧结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StackFrameExample {

public int calculate(int a, int b) {
// 栈帧包含:
// 1. 局部变量表:a, b, result
// 2. 操作数栈:用于计算
// 3. 方法返回地址:返回调用者
int result = a + b;
return result;
}

public void methodCall() {
int x = 10;
int y = 20;
int sum = calculate(x, y); // 创建新的栈帧
System.out.println(sum);
}
}

栈帧结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────┐
│ 栈帧(Stack Frame) │
│ ┌──────────────────────────────┐ │
│ │ 局部变量表(Local Variables) │ │
│ │ - 方法参数 │ │
│ │ - 局部变量 │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 操作数栈(Operand Stack) │ │
│ │ - 计算中间结果 │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 动态链接(Dynamic Linking) │ │
│ │ - 方法引用 │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 方法返回地址(Return Address)│ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘

栈内存参数

1
2
# 栈内存参数
-Xss1m # 每个线程栈大小(-X stack size)

3.3 方法区

3.3.1 方法区内容

方法区:存储类信息、常量池、静态变量等。

方法区内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MethodAreaExample {

// 静态变量:存储在方法区
private static String staticField = "Static Field";

// 常量:存储在方法区常量池
private static final int CONSTANT = 100;

// 类信息:存储在方法区
// - 类的全限定名
// - 类的直接父类
// - 类的修饰符
// - 类的字段、方法信息
}

方法区(元空间)参数

1
2
3
4
5
6
7
# Java 8之前:永久代(PermGen)
-XX:PermSize=256m
-XX:MaxPermSize=512m

# Java 8+:元空间(Metaspace)
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

3.4 程序计数器

3.4.1 PC Register

程序计数器:记录当前线程执行的字节码指令地址。

程序计数器特点

  • 线程私有
  • 唯一不会OOM的区域
  • 记录当前执行指令地址

3.5 本地方法栈

3.5.1 Native Stack

本地方法栈:为Native方法服务,类似Java栈。


4. 垃圾回收

4.1 对象存活判断

4.1.1 引用计数法

引用计数法:对象被引用时计数+1,引用失效时计数-1,计数为0时回收。

问题:无法解决循环引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReferenceCounting {

public void circularReference() {
Object obj1 = new Object();
Object obj2 = new Object();

// 循环引用
obj1.ref = obj2;
obj2.ref = obj1;

obj1 = null;
obj2 = null;

// 引用计数法无法回收循环引用的对象
}
}

4.1.2 可达性分析

可达性分析:从GC Roots开始,标记所有可达对象,未标记的对象为垃圾。

GC Roots

  1. 虚拟机栈中引用的对象
  2. 方法区中静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中引用的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReachabilityAnalysis {

// GC Root 1: 静态变量
private static Object staticObj = new Object();

// GC Root 2: 常量
private static final Object CONSTANT_OBJ = new Object();

public void method() {
// GC Root 3: 局部变量(栈中引用)
Object localObj = new Object();

// 这些对象都是GC Roots,不会被回收
}
}

4.2 垃圾回收算法

4.2.1 标记-清除算法

标记-清除(Mark-Sweep)

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

缺点

  • 效率低
  • 产生内存碎片
1
2
3
4
5
6
7
8
标记前:
[对象1][对象2][对象3][对象4]
✓ ✗ ✓ ✗

清除后:
[对象1] [对象3]
↑ ↑
内存碎片

4.2.2 标记-复制算法

标记-复制(Mark-Copy)

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

优点:无内存碎片
缺点:内存利用率低(50%)

1
2
3
4
5
6
复制前(Eden):
[对象1][对象2][对象3][对象4]
✓ ✗ ✓ ✗

复制后(Survivor):
[对象1][对象3]

4.2.3 标记-整理算法

标记-整理(Mark-Compact)

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

优点:无内存碎片,内存利用率高
缺点:效率较低

1
2
3
4
5
6
整理前:
[对象1][对象2][对象3][对象4]
✓ ✗ ✓ ✗

整理后:
[对象1][对象3] [空闲]

4.3 分代收集

4.3.1 分代策略

分代收集:根据对象存活周期,将堆分为新生代和老年代。

新生代

  • 对象存活时间短
  • 使用标记-复制算法
  • 回收频繁

老年代

  • 对象存活时间长
  • 使用标记-清除或标记-整理算法
  • 回收频率低

对象晋升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GenerationalGC {

public void objectPromotion() {
// 1. 对象在Eden区分配
Object obj = new Object();

// 2. Minor GC:Eden区满时触发
// - 存活对象复制到Survivor区
// - 对象年龄+1

// 3. 对象在Survivor区之间复制
// - 每经历一次Minor GC,年龄+1

// 4. 对象晋升到老年代
// - 年龄达到阈值(默认15)
// - Survivor区空间不足
// - 大对象直接进入老年代
}
}

4.4 垃圾回收器

4.4.1 Serial收集器

Serial收集器:单线程,适合小应用。

特点

  • 单线程收集
  • 会Stop The World(STW)
  • 适合客户端应用
1
2
# 使用Serial收集器
-XX:+UseSerialGC

4.4.2 Parallel收集器

Parallel收集器:多线程,适合吞吐量优先的应用。

特点

  • 多线程收集
  • 会Stop The World
  • 适合后台应用
1
2
3
# 使用Parallel收集器
-XX:+UseParallelGC
-XX:ParallelGCThreads=4 # GC线程数

4.4.3 CMS收集器

CMS(Concurrent Mark Sweep)收集器:并发标记清除,适合响应时间优先的应用。

特点

  • 并发收集
  • 低停顿
  • 会产生内存碎片

CMS收集过程

  1. 初始标记(STW)
  2. 并发标记
  3. 重新标记(STW)
  4. 并发清除
1
2
3
# 使用CMS收集器
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70 # 老年代使用率70%时触发

4.4.4 G1收集器

G1(Garbage First)收集器:面向服务端,低延迟。

特点

  • 将堆分为多个Region
  • 可预测的停顿时间
  • 适合大堆内存

G1收集过程

  1. 初始标记(STW)
  2. 并发标记
  3. 最终标记(STW)
  4. 筛选回收(STW)
1
2
3
4
# 使用G1收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 最大GC暂停时间(毫秒)
-XX:G1HeapRegionSize=16m # Region大小

G1 Region划分

1
2
3
4
5
6
7
8
┌─────────────────────────────────────┐
│ G1堆(多个Region) │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ E │ │ S │ │ O │ │ H │ │空 │ │
│ └───┘ └───┘ └───┘ └───┘ └───┘ │
│ E: Eden S: Survivor │
│ O: Old H: Humongous │
└─────────────────────────────────────┘

4.4.5 ZGC收集器

ZGC收集器:低延迟,适合大堆内存。

特点

  • 停顿时间不超过10ms
  • 支持TB级堆内存
  • Java 11+支持
1
2
# 使用ZGC收集器(Java 11+)
-XX:+UseZGC

4.5 GC日志分析

4.5.1 GC日志配置

GC日志参数

1
2
3
4
5
6
7
8
9
10
11
12
13
# 打印GC日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

# G1 GC日志
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:gc.log

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

GC日志示例

1
2
3
4
[2024-01-01T10:00:00.123+0800] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[2024-01-01T10:00:00.123+0800] GC(0) Using 8 workers of 8 for evacuation
[2024-01-01T10:00:00.145+0800] GC(0) [Eden: 512M->0B(512M) Survivors: 0B->64M Heap: 512M->450M(2G)]
[2024-01-01T10:00:00.145+0800] GC(0) [Times: user=0.15 sys=0.02, real=0.02 secs]

5. 类加载机制

5.1 类加载过程

5.1.1 加载阶段

类加载过程

  1. 加载(Loading):读取类文件
  2. 验证(Verification):验证类文件格式
  3. 准备(Preparation):为静态变量分配内存
  4. 解析(Resolution):将符号引用转为直接引用
  5. 初始化(Initialization):执行类构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClassLoadingExample {

// 静态变量:在准备阶段分配内存,初始值为0
private static int staticVar;

// 静态常量:在准备阶段分配内存,初始值为100
private static final int CONSTANT = 100;

// 静态代码块:在初始化阶段执行
static {
staticVar = 10;
System.out.println("Static block executed");
}
}

5.2 类加载器

5.2.1 类加载器层次

类加载器层次

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

Extension ClassLoader (扩展类加载器)

Application ClassLoader (应用类加载器)

Custom ClassLoader (自定义类加载器)

类加载器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ClassLoaderExample {

public void showClassLoaders() {
// 1. 应用类加载器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("Application ClassLoader: " + appClassLoader);

// 2. 扩展类加载器
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("Extension ClassLoader: " + extClassLoader);

// 3. 启动类加载器(null,由C++实现)
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("Bootstrap ClassLoader: " + bootstrapClassLoader);

// 4. 当前类的类加载器
ClassLoader currentClassLoader = this.getClass().getClassLoader();
System.out.println("Current ClassLoader: " + currentClassLoader);
}
}

5.3 双亲委派模型

5.3.1 双亲委派机制

双亲委派模型:类加载请求先委派给父类加载器,父类加载器无法加载时,才由子类加载器加载。

双亲委派流程

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
public class ParentDelegationModel {

// 双亲委派流程
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 1. 检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c != null) {
return c;
}

// 2. 委派给父类加载器
try {
if (parent != null) {
c = parent.loadClass(name);
if (c != null) {
return c;
}
}
} catch (ClassNotFoundException e) {
// 父类加载器无法加载
}

// 3. 父类加载器无法加载,自己加载
return findClass(name);
}
}

双亲委派优势

  1. 避免类重复加载
  2. 保证核心类安全(如java.lang.Object)

5.4 自定义类加载器

5.4.1 实现自定义类加载器

自定义类加载器

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
public class CustomClassLoader extends ClassLoader {

private String classPath;

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

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 1. 读取类文件
byte[] classData = loadClassData(name);

// 2. 定义类
return defineClass(name, classData, 0, classData.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}

private byte[] loadClassData(String name) throws IOException {
String path = classPath + name.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}

6. 字节码执行

6.1 字节码指令

6.1.1 常见字节码指令

字节码指令分类

  1. 加载和存储:iload, istore, aload, astore
  2. 运算指令:iadd, isub, imul, idiv
  3. 类型转换:i2l, i2f, i2d
  4. 对象操作:new, getfield, putfield
  5. 方法调用:invokevirtual, invokespecial, invokestatic

字节码示例

1
2
3
4
5
6
7
public class BytecodeExample {

public int calculate(int a, int b) {
int result = a + b;
return result;
}
}

对应字节码

1
2
3
4
5
6
7
8
public int calculate(int, int);
Code:
0: iload_1 // 加载局部变量a(第1个参数)
1: iload_2 // 加载局部变量b(第2个参数)
2: iadd // 相加
3: istore_3 // 存储到result(局部变量3)
4: iload_3 // 加载result
5: ireturn // 返回

6.2 执行引擎

6.2.1 解释执行和编译执行

执行引擎

  1. 解释执行:逐条解释字节码
  2. 编译执行:将字节码编译为机器码(JIT)

JIT编译

1
2
3
4
5
6
7
8
9
10
public class JITExample {

// 热点代码会被JIT编译为机器码
public void hotMethod() {
for (int i = 0; i < 1000000; i++) {
// 这段代码会被JIT编译
int result = i * 2;
}
}
}

JIT参数

1
2
3
4
5
# 启用JIT编译
-XX:+TieredCompilation

# 编译阈值
-XX:CompileThreshold=10000 # 方法调用次数达到10000次时编译

7. 性能调优

7.1 内存调优

7.1.1 堆内存调优

堆内存调优

1
2
3
4
5
6
7
8
9
10
# 堆内存设置
-Xms2g # 初始堆大小(建议与-Xmx相同,避免动态扩容)
-Xmx4g # 最大堆大小
-Xmn1g # 新生代大小

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

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

调优建议

  1. -Xms和-Xmx设置相同:避免动态扩容
  2. 新生代大小:通常为堆的1/3到1/4
  3. Survivor比例:根据对象存活时间调整

7.2 GC调优

7.2.1 GC参数调优

G1 GC调优

1
2
3
4
5
# G1 GC参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 最大GC暂停时间
-XX:G1HeapRegionSize=16m # Region大小
-XX:InitiatingHeapOccupancyPercent=45 # 堆使用率45%时开始并发标记

Parallel GC调优

1
2
3
4
5
# Parallel GC参数
-XX:+UseParallelGC
-XX:ParallelGCThreads=4 # GC线程数
-XX:MaxGCPauseMillis=200 # 最大GC暂停时间
-XX:GCTimeRatio=19 # GC时间占比(1/(1+19)=5%)

7.3 监控工具

7.3.1 JVM监控工具

常用监控工具

  1. jps:查看Java进程
  2. jstat:查看GC统计
  3. jmap:生成堆转储
  4. jstack:生成线程转储
  5. jvisualvm:可视化监控

jstat使用

1
2
3
4
5
6
# 查看GC统计(每1秒打印一次,共10次)
jstat -gc <pid> 1000 10

# 输出示例
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
0.0 1024.0 0.0 1024.0 8192.0 8192.0 20480.0 1024.0 4864.0 4096.0 512.0 256.0 10 0.100 2 0.200 0.300

jmap使用

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

# 查看堆内存使用
jmap -heap <pid>

jstack使用

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

# 查看线程状态
jstack -l <pid>

7.4 常见问题排查

7.4.1 OOM问题

OutOfMemoryError类型

  1. Java heap space:堆内存不足
  2. Metaspace:元空间不足
  3. Direct buffer memory:直接内存不足
  4. Unable to create new native thread:线程数过多

OOM排查

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

# 2. 使用jvisualvm或MAT分析堆转储
# 3. 查看大对象和引用关系

7.4.2 CPU高问题

CPU高排查

1
2
3
4
5
6
7
# 1. 查看线程CPU使用
top -H -p <pid>

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

# 3. 分析线程状态,找出占用CPU的线程

7.4.3 GC频繁问题

GC频繁排查

1
2
3
4
5
# 1. 查看GC日志
jstat -gc <pid> 1000 10

# 2. 分析GC频率和耗时
# 3. 调整堆内存大小和GC参数

8. 实战案例

8.1 内存泄漏排查

8.1.1 内存泄漏案例

内存泄漏示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MemoryLeakExample {

// 问题:静态集合持有对象引用,导致对象无法回收
private static List<Object> cache = new ArrayList<>();

public void addToCache(Object obj) {
cache.add(obj); // 对象被cache持有,无法回收
}

// 解决:使用WeakReference
private static List<WeakReference<Object>> weakCache = new ArrayList<>();

public void addToWeakCache(Object obj) {
weakCache.add(new WeakReference<>(obj));
}
}

排查步骤

  1. 使用jmap生成堆转储
  2. 使用MAT分析大对象
  3. 查看对象引用关系
  4. 找出泄漏原因

8.2 GC调优案例

8.2.1 GC调优实战

场景:应用响应慢,GC频繁。

调优步骤

  1. 收集GC日志

    1
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
  2. 分析GC日志

    • GC频率:每分钟多少次
    • GC耗时:每次GC耗时
    • 堆使用率:GC前后堆使用率
  3. 调整参数

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

    # 调整新生代大小
    -Xmn2g

    # 使用G1 GC
    -XX:+UseG1GC -XX:MaxGCPauseMillis=200
  4. 验证效果:对比调优前后的GC指标


9. 总结

9.1 核心要点

  1. 内存模型:理解堆、栈、方法区的作用
  2. 垃圾回收:掌握GC算法和垃圾回收器
  3. 类加载:理解类加载过程和双亲委派
  4. 性能调优:掌握JVM调优方法和工具

9.2 关键理解

  1. 堆是GC主要区域:大部分对象在堆中分配和回收
  2. 分代收集:根据对象存活周期采用不同策略
  3. 双亲委派:保证类加载安全和避免重复加载
  4. JIT编译:热点代码会被编译为机器码

9.3 最佳实践

  1. 合理设置堆内存:根据应用特点设置
  2. 选择合适的GC:根据应用特点选择GC
  3. 监控GC指标:定期监控GC频率和耗时
  4. 及时排查问题:OOM、CPU高等问题及时排查

相关文章