Java虚拟机(JVM)内存模型分为两部分:
1. JVM内存结构(运行时数据区域):描述JVM内存的物理划分;
2. Java内存模型(JMM, Java Memory Model):定义多线程环境下内存访问的规则和保障机制。
一、JVM内存结构(运行时数据区域)
JVM将内存划分为以下核心区域,每个区域有明确的功能和生命周期:
1. 程序计数器(Program Counter Register)
- 作用:记录当前线程执行的字节码指令地址(分支、循环、跳转等)。
- 特点:
- 线程私有,生命周期与线程绑定。
- 唯一不会发生内存溢出(OOM)的区域。
- 示例场景:多线程切换时,保存当前线程执行位置以便恢复。
2. 虚拟机栈(Java Virtual Machine Stacks)
- 作用:存储方法调用的栈帧(局部变量表、操作数栈、动态链接、方法出口等)。
- 线程私有:每个线程有自己的栈。
- 栈帧结构:
- 局部变量表:存放基本类型(
int
、boolean
等)和对象引用。 - 操作数栈:执行字节码指令的临时数据存储区。
- 动态链接:指向运行时常量池的方法引用。
- 方法出口:记录方法返回地址。
- 局部变量表:存放基本类型(
- 异常:
- StackOverflowError:栈深度超出限制(如递归调用未终止)。
- OOM:无法申请足够内存扩展栈(较少见)。
- 配置参数:
-Xss
(如-Xss1m
设置栈大小为1MB)。
3. 本地方法栈(Native Method Stack)
- 作用:为JVM调用本地(Native)方法(如C/C++库)服务。
- 特点:与虚拟机栈类似,但服务于Native方法。
- 异常:同虚拟机栈。
4. 堆(Heap)
- 作用:存储所有对象实例和数组(
new
关键字创建的对象)。 - 线程共享:所有线程共享堆内存。
- 分区(JDK 8及之后):
- 新生代(Young Generation):
- Eden区:对象初次分配区域。
- Survivor区(S0/S1):存放经过Minor GC后存活的对象。
- 老年代(Old Generation):长期存活的对象(经过多次GC后晋升)。
- 元空间(Metaspace,JDK 8+):取代永久代(PermGen),存储类元数据、常量池等。
- 新生代(Young Generation):
- GC机制:
- Minor GC:清理新生代。
- Major GC/Full GC:清理整个堆(包括老年代)。
- 异常:
OutOfMemoryError
(堆内存不足)。 - 配置参数:
-Xms
:初始堆大小(如-Xms256m
)。-Xmx
:最大堆大小(如-Xmx2g
)。-XX:NewRatio
:新生代与老年代比例(如-XX:NewRatio=2
表示老年代是新生代的2倍)。
5. 方法区(Method Area)
- 作用:存储类信息、常量、静态变量、即时编译器编译后的代码。
- 实现演变:
- JDK 7及之前:永久代(PermGen),存在OOM风险。
- JDK 8+:元空间(Metaspace),使用本地内存(Native Memory),默认无上限。
- 异常:
OutOfMemoryError
(元空间内存不足)。 - 配置参数:
-XX:MetaspaceSize
:初始元空间大小。-XX:MaxMetaspaceSize
:最大元空间大小(默认无限制)。
6. 运行时常量池(Runtime Constant Pool)
- 作用:存放类文件中的常量(字面量、符号引用等)。
- 位置:方法区的一部分。
- 示例:字符串常量池(String Table)位于堆中(JDK 7+)。
二、Java内存模型(JMM)
JMM定义了多线程环境下共享变量的访问规则,解决可见性、原子性、有序性问题。
1. 主内存与工作内存
- 主内存:所有线程共享的堆内存。
- 工作内存:线程私有的本地内存(缓存、寄存器等),存储共享变量的副本。
- 交互规则:
- 线程对共享变量的操作需通过工作内存与主内存交互。
- 线程间不可直接访问对方的工作内存。
2. 内存屏障(Memory Barriers)
JMM通过内存屏障禁止指令重排序,保障可见性和有序性:
- Load Barrier:确保读取操作前所有其他内存操作已完成。
- Store Barrier:确保写入操作对其他线程可见。
3. Happens-Before规则
定义操作间的先行关系,确保多线程执行的逻辑正确性:
- 程序顺序规则:同一线程内的操作按代码顺序执行。
- 锁规则:解锁操作先行于后续的加锁操作。
- volatile规则:
volatile
变量的写操作先行于后续的读操作。 - 传递性规则:若A先于B,B先于C,则A先于C。
4. volatile关键字
- 可见性:强制线程从主内存读取最新值,修改后立即刷新到主内存。
- 禁止指令重排序:通过插入内存屏障实现。
- 适用场景:状态标志位(如
boolean flag
)、单例模式的双重检查锁(Double-Checked Locking)。
5. synchronized与锁
- 原子性:通过锁机制保障代码块或方法的原子执行。
- 可见性:解锁前将变量刷新到主内存,加锁时清空工作内存中的副本。
6. final字段的内存语义
- 初始化安全:正确构造的对象,其
final
字段对所有线程可见。 - 禁止重排序:构造函数内对
final
字段的写入不会重排序到构造函数外。
三、常见问题与调优
1. 内存溢出(OOM)场景
- 堆溢出:对象过多(如大列表未释放)。
- 元空间溢出:加载过多类(如动态生成类未卸载)。
- 栈溢出:深度递归调用。
2. 调优建议
- 堆内存:根据应用需求设置
-Xms
和-Xmx
,避免频繁Full GC。 - 线程栈:减少
-Xss
值(如256k)以支持更多线程。 - 元空间:限制
-XX:MaxMetaspaceSize
防止无限增长。 - GC策略:根据停顿时间和吞吐量需求选择G1、ZGC等垃圾回收器。
四、示例代码与参数配置
public class MemoryModelDemo {
private static volatile boolean flag = false; // volatile保障可见性
public static void main(String[] args) {
new Thread(() -> {
while (!flag) {} // 循环直到flag为true
System.out.println("Flag changed to true");
}).start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; // 修改flag值
}).start();
}
}
启动参数示例:
java -Xms512m -Xmx2g -Xss256k -XX:MaxMetaspaceSize=256m MemoryModelDemo
总结
- 内存结构:理解堆、栈、方法区等区域的功能和配置是调优的基础。
- JMM规则:掌握
volatile
、synchronized
和happens-before
规则是编写正确并发程序的关键。 - 调优目标:平衡内存分配、GC频率和线程并发能力,避免OOM和性能瓶颈。