JVM 内存模型详解
JVM 运行时数据区是 Java 程序执行的基础,理解内存结构对性能优化和问题排查至关重要。
一、整体架构

JVM 运行时数据区分为五大部分:
| 区域 | 用途 | 线程共享 | 异常 |
|---|---|---|---|
| 程序计数器 | 记录执行位置 | 私有 | 无 |
| Java 虚拟机栈 | 方法调用栈帧 | 私有 | StackOverflowError / OutOfMemoryError |
| 本地方法栈 | Native 方法 | 私有 | StackOverflowError / OutOfMemoryError |
| 堆 | 对象实例 | 共享 | OutOfMemoryError |
| 方法区 | 类元数据 | 共享 | OutOfMemoryError |
二、程序计数器(PC Register)
2.1 作用
- 记录当前线程执行的字节码指令地址
- 线程切换后能恢复正确的执行位置
- 唯一没有 OOM 的区域
2.2 特点
每个线程独立
↓
存储当前执行的字节码行号
↓
执行 Native 方法时为 undefined
三、Java 虚拟机栈
3.1 栈帧结构
栈帧(Stack Frame)
├── 局部变量表(Local Variables)
├── 操作数栈(Operand Stack)
├── 动态链接(Dynamic Linking)
└── 方法返回地址(Return Address)
3.2 局部变量表
public void method(int a, long b, String c) {
int d = 10; // 局部变量
}
Slot 分配:
- int/float/引用:1 个 Slot
- long/double:2 个 Slot
3.3 操作数栈
// i = a + b * c
iload_1 // 加载 a
iload_2 // 加载 b
iload_3 // 加载 c
imul // b * c
iadd // a + (b * c)
istore 4 // 存储到 i
3.4 常见异常
// StackOverflowError - 栈深度超限
public void recursive() {
recursive(); // 无限递归
}
// OutOfMemoryError - 栈容量不足
// -Xss 设置过小,线程数过多
四、本地方法栈
4.1 作用
- 为 Native 方法服务
- JVM 实现时可与方法区合并
- HotSpot 中与虚拟机栈合二为一
4.2 Native 方法示例
// System.currentTimeMillis() 是 Native 方法
public static native long currentTimeMillis();
// 底层调用操作系统 API
五、堆(Heap)
5.1 堆结构
堆内存
├── 新生代(Young Generation)
│ ├── Eden 区(8)
│ ├── Survivor0 区(1)
│ └── Survivor1 区(1)
└── 老年代(Old Generation)
└── 大对象可直接进入老年代
比例:Eden : Survivor0 : Survivor1 = 8 : 1 : 1
5.2 对象分配流程
flowchart TD
A[新对象] --> B{Eden 区<br>有空间?}
B -->|是 | C[分配 Eden]
B -->|否 | D[Minor GC]
D --> E{Survivor<br>有空间?}
E -->|是 | F[移动到 Survivor]
E -->|否 | G[进入老年代]
5.3 对象内存布局
对象头(Header)
├── Mark Word(对象哈希、GC 信息、锁状态)
├── Klass Pointer(类型指针)
└── 数组长度(可选)
实例数据(Instance Data)
└── 继承的字段 + 自身字段
对齐填充(Padding)
└── 8 字节对齐
5.4 常见异常
# OutOfMemoryError: Java heap space
# 原因:对象过多,内存不足
# 解决:-Xmx 增大堆内存,排查内存泄漏
# OutOfMemoryError: GC overhead limit exceeded
# 原因:GC 时间超过 98%,回收内存不到 2%
# 解决:优化代码,减少对象创建
六、方法区(Method Area)
6.1 存储内容
方法区
├── 类信息(版本、字段、方法、接口)
├── 常量池(字符串常量、类引用)
├── 静态变量
├── 即时编译代码缓存
└── RunTimeConstantPool
6.2 运行时常量池
public class Test {
// 编译期常量
public static final int A = 1;
// 运行时常量池
String s1 = "abc"; // 字符串常量池
String s2 = "abc"; // 同一引用
}
6.3 字符串常量池
String s1 = "abc"; // 常量池
String s2 = new String("abc"); // 堆中创建
System.out.println(s1 == s2); // false
System.out.println(s1 == s2.intern()); // true
6.4 元空间(Metaspace)
JDK 8 变化:
- 永久代 → 元空间
- 使用本地内存,不再占用堆内存
- 默认不限制大小(可用
-XX:MaxMetaspaceSize限制)
七、内存参数配置
7.1 常用参数
# 堆内存
-Xms256m # 初始堆大小
-Xmx512m # 最大堆大小
-Xmn128m # 新生代大小
# 栈内存
-Xss256k # 线程栈大小
# 方法区/元空间
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
# GC 相关
-XX:+PrintGCDetails
-XX:+UseG1GC # 使用 G1 收集器
7.2 内存查看
# 查看进程
jps -l
# 查看内存使用
jstat -gc <pid> 1000
# 内存 dump
jmap -dump:format=b,file=heap.hprof <pid>
# 分析工具
jhat heap.hprof # JDK 自带
MAT # Eclipse Memory Analyzer
八、内存泄漏排查
8.1 常见原因
- 静态集合:static Map/List 持有对象引用
- 未关闭资源:IO 流、数据库连接
- 监听器未注销:回调接口未移除
- ThreadLocal 未清理:线程复用导致
8.2 排查步骤
1. 监控告警 → 2. dump 内存 → 3. MAT 分析 → 4. 定位代码
8.3 示例
// ❌ 内存泄漏
static List<Object> cache = new ArrayList<>();
public void add(Object obj) {
cache.add(obj); // 只增不减
}
// ✅ 修复
WeakReference<Object> ref = new WeakReference<>(obj);
九、总结
JVM 内存核心要点:
| 区域 | 作用 | 线程 | 异常 |
|---|---|---|---|
| PC 寄存器 | 指令地址 | 私有 | 无 |
| 虚拟机栈 | 方法调用 | 私有 | StackOverflowError |
| 本地方法栈 | Native 方法 | 私有 | StackOverflowError |
| 堆 | 对象实例 | 共享 | OutOfMemoryError |
| 方法区 | 类元数据 | 共享 | OutOfMemoryError |
理解内存结构是性能优化和问题排查的基础,建议结合 jstat、MAT 等工具实践。