JVM 内存结构的本质,不是"区域划分", 而是为了支撑 字节码执行模型与多线程并发模型 的工程实现。
在理解任何 JVM 内存知识之前,必须先回答一个问题:
为什么 JVM 需要这样一套内存结构?
其根本原因来自三个核心需求:
JVM 的运行本质是:
因此必须提供:
JVM 是一个天然的多线程执行环境:
因此需要:
JVM 的核心价值之一是:
自动内存管理(GC)
这要求:
基于以上三点需求,可以自然推导出 JVM 内存结构的本质模型:
JVM 中的所有运行时数据,本质上只有两类:
| 数据类型 | 本质 |
|---|---|
| 执行上下文 | 描述“代码执行到哪里” |
| 对象数据 | 描述“程序处理的数据” |
于是自然形成两大区域:
JVM 内存结构
├── 线程私有区(执行上下文)
│ ├── 程序计数器(PC)
│ ├── 虚拟机栈(JVM Stack)
│ └── 本地方法栈(Native Stack)
│
└── 线程共享区(数据存储)
├── 堆(Heap)
├── 方法区(Method Area)
├── 运行时常量池
└── 直接内存
这一结构并非偶然,而是:
JVM 执行模型的必然产物
线程私有区的本质是:
描述“一个线程正在如何执行 Java 代码”
程序计数器的本质是:
线程级的执行状态指针
JVM 多线程的执行方式是:
为了在切换回来时能“继续执行”,必须保存:
当前线程执行到了哪一条字节码指令
PC 的核心职责:
它是 JVM 中唯一:
不会发生 OOM 的内存区域
因为它只需要保存一个指针。
JVM 栈的本质是:
对“方法调用关系”的运行时建模
Java 程序的执行是:
因此需要一个结构来表示:
方法调用链
这个结构就是:
虚拟机栈 + 栈帧
每个方法调用对应一个栈帧:
栈帧
├── 局部变量表(Local Variables)
├── 操作数栈(Operand Stack)
├── 动态连接(Dynamic Linking)
└── 返回地址(Return Address)
| 组件 | 本质 |
|---|---|
| 局部变量表 | 方法的私有数据空间 |
| 操作数栈 | 字节码指令的计算暂存区 |
| 动态连接 | 与常量池的运行时绑定 |
| 返回地址 | 方法调用链的恢复点 |
由于栈是线程私有且容量有限:
本地方法栈的本质与 JVM 栈一致:
只不过服务对象是 Native 方法
它体现的是:
Java 世界与 Native 世界的执行边界
线程共享区的本质是:
存储“程序真正处理的数据”
堆的本质是:
对象生命周期管理区
因为:
Heap
├── Young
│ ├── Eden
│ ├── S0
│ └── S1
└── Old
其本质假设是:
大部分对象“朝生夕死”
这是 GC 分代设计的理论基础。
TLAB 的设计本质是:
用空间换并发性能
避免对象分配时的全局锁竞争。
方法区的本质是:
JVM 的“类型信息仓库”
存储:
| 阶段 | 实现 |
|---|---|
| JDK7 之前 | PermGen |
| JDK8 之后 | Metaspace |
这一变化的本质原因是:
将类元数据的存储与 Java 堆解耦
运行时常量池是:
常量的运行时表示
是方法区的一部分逻辑概念。
不仅仅是编译期常量:
String.intern()
体现了:
常量池的运行时扩展能力
直接内存的本质是:
进程地址空间中的 Native Heap
对象是 JVM 内存管理的核心单元。
类加载 → 内存分配 → 初始化 → 执行构造
Object obj = new Object();
对应:
这体现的是:
JVM 对象创建的指令级协议
对象在内存中的本质结构:
对象
├── 对象头
│ ├── Mark Word
│ └── 类型指针
├── 实例数据
└── 对齐填充
Mark Word 是:
对象的运行时元信息载体
承载:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
本质是:
从“乐观”到“悲观”的并发策略演进
两种模型的本质权衡:
| 方式 | 本质 |
|---|---|
| 句柄 | 稳定但多一次间接访问 |
| 直接指针 | 高性能 |
HotSpot 选择:
直接指针模型
所有 OOM 问题本质上可以归为:
| 区域 | 本质原因 |
|---|---|
| 堆 OOM | 对象过多或泄漏 |
| 栈 OOM | 调用过深 |
| 方法区 OOM | 类过多 |
| 直接内存 OOM | Native 内存耗尽 |
分析内存问题的本质逻辑:
现象 → 区域定位 → 根因分析
工具本质是:
对 JVM 内存模型的观测手段
| 工具 | 本质 |
|---|---|
| jstat | 运行时状态观测 |
| jmap | 堆数据快照 |
| jstack | 线程执行状态 |
| MAT | 对象引用分析 |
这些属于:
不稳定知识层 服务于稳定原理层
JVM 内存结构不是随意设计的,而是:
执行模型驱动的必然结果
| 区域 | 本质 |
|---|---|
| PC | 线程执行指针 |
| 栈 | 方法调用上下文 |
| 堆 | 对象生命周期 |
| 方法区 | 类型系统 |
| 直接内存 | 高性能 I/O |
字节码执行模型
↓
多线程并发需求
↓
执行上下文隔离
↓
对象数据共享
↓
JVM 内存结构
理解 JVM 内存结构,不应从:
"有哪些区域"
入手,而应从:
"为什么需要这些区域"
入手。