Java JVM内存结构
JVM运行时数据区是程序运行的数据存储区域,不同区域有不同用途。
JVM内存区域划分
Java
┌─────────────────────────────────────────────────┐
│ 运行时数据区 │
├─────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 程序计数器 │ │ Java栈 │ │
│ │ (线程私有) │ │ (线程私有) │ │
│ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 本地方法栈 │ │ 方法区/元空间 │ │
│ │ (线程私有) │ │ (线程共享) │ │
│ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ 堆 │ │
│ │ (线程共享) │ │
│ │ ┌─────────┬─────────┬─────────┐ │ │
│ │ │ 年轻代 │ 年老代 │ 元空间 │ │ │
│ │ │Eden+Surv│ │(JDK8+) │ │ │
│ │ └─────────┴─────────┴─────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
各内存区域详解
1. 程序计数器
作用:记录当前线程执行到哪条字节码指令。
Java
// 多线程执行时,每个线程有独立的程序计数器
// 纰程切换后能恢复到正确的执行位置
特点:
- 线程私有
- 唯一不会OOM的区域
- 执行native方法时为空
2. Java栈
作用:存储方法调用和局部变量。
Java
public int add(int a, int b) {
int sum = a + b; // 局部变量在栈帧中
return sum;
}
栈帧结构:
Java
┌─────────────┐
│ 栈帧 │
├─────────────┤
│ 局部变量表 │ ← 方法参数、局部变量
│ 操作数栈 │ ← 计算中间结果
│ 动态链接 │ ← 方法引用
│ 返回地址 │ ← 方法返回位置
└─────────────┘
特点:
- 线程私有
- 栈溢出:StackOverflowError(递归过深)
- OOM:OutOfMemoryError(创建过多线程)
3. 本地方法栈
作用:执行本地方法(Native Method)。
Java
// Thread.java中的本地方法
private native void start0();
特点:
- 线程私有
- 与Java栈类似,但执行本地方法
4. 堆
作用:存储对象实例和数组。
Java
Object obj = new Object(); // 对象在堆中
int[] arr = new int[100]; // 数组在堆中
堆结构:
Bash
┌───────────────────────────────────────┐
│ 堆 │
├───────────────────────────────────────┤
│ ┌─────────────────────────────┐ │
│ │ 年轻代(Young Gen) │ │
│ │ ┌───────┬───────┬───────┐ │ │
│ │ │ Eden │ S0 │ S1 │ │ │
│ │ │ │Survivor│Survivor│ │ │
│ │ └───────┴───────┴───────┘ │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 老年代(Old Gen) │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────┘
特点:
- 线程共享
- GC主要区域
- 可设置大小:-Xms、-Xmx
5. 方法区/元空间
作用:存储类信息、常量、静态变量。
text
public class MyClass {
private static int count; // 静态变量在方法区
private static final int MAX; // 常量在方法区
}
存储内容:
- 类信息:类名、父类、接口、方法
- 运行时常量池:字符串常量、符号引用
- 静态变量
- JIT编译后的代码
演变:
| JDK版本 | 名称 | 位置 |
|---|---|---|
| JDK 7 | 永久代 | JVM堆的一部分 |
| JDK 8+ | 元空间 | 本地内存 |
特点:
- 线程共享
- JDK 8+使用元空间,不再受堆大小限制
直接内存
作用:NIO使用的堆外内存。
text
// NIO直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
特点:
- 不受JVM堆大小限制
- 减少GC压力
- 分配/释放开销较大
内存区域对比
| 区域 | 线程归属 | 存储内容 | GC | 异常 |
|---|---|---|---|---|
| 程序计数器 | 私有 | 指令位置 | 无 | 无 |
| Java栈 | 私有 | 栈帧 | 无 | StackOverflowError |
| 本地方法栈 | 私有 | 本地方法栈帧 | 无 | StackOverflowError |
| 堆 | 共享 | 对象 | 有 | OutOfMemoryError |
| 方法区 | 共享 | 类信息 | 有(JDK8+) | OutOfMemoryError |
JVM内存参数
text
# 堆大小
-Xms512m # 初始堆大小
-Xmx1024m # 最大堆大小
# 年轻代
-Xmn256m # 年轻代大小
-XX:SurvivorRatio=8 # Eden:S0:S1比例
# 元空间
-XX:MetaspaceSize=128m # 元空间初始大小
-XX:MaxMetaspaceSize=256m # 元空间最大大小
# 栈大小
-Xss256k # 每个线程栈大小
注意事项
堆是GC主要区域,优化重点
方法区/元空间存储类信息,类太多会OOM
栈深度过大会StackOverflowError
直接内存不在堆中,但受-XX:MaxDirectMemorySize限制
要点总结
- JVM内存区域:程序计数器、Java栈、本地方法栈、堆、方法区
- 程序计数器、栈是线程私有;堆、方法区是线程共享
- 堆存储对象,是GC主要区域
- 方法区存储类信息,JDK 8+改为元空间
- 合理设置-Xms、-Xmx、-Xmn等参数优化内存
📝 发现内容有误?点击此处直接编辑