+--------------------------------------------------+
| JVM 运行时数据区 |
+----------------------+---------------------------+
| 线程私有 | 线程共享 |
|----------------------|---------------------------|
| • 程序计数器 | • 堆(Heap) |
| • 虚拟机栈 | - 新生代(Eden, S0, S1)|
| • 本地方法栈 | - 老年代 |
| | • 方法区(Metaspace) |
+----------------------+---------------------------+
+--------------------------------------------------+
| 直接内存(Direct Memory) |
|(不属于 JVM 规范定义,但常被使用) |
+--------------------------------------------------+堆
最大、最重要的内存区域
存放对象实例、数组等。几乎所有的对象都在堆上分配(逃逸分析优化后可能栈上分配)。所有线程共享。
特点
| 特性 | 说明 |
|---|---|
| 线程共享 | 所有线程均可访问堆中的对象 |
| 动态分配 | 对象在运行时动态创建,大小不固定 |
| 自动管理 | 由 JVM 的垃圾回收器(GC)自动回收不再使用的对象 |
| 可扩展 | 可通过 -Xms / -Xmx 设置初始和最大堆大小 |
| 物理不连续 | 逻辑上连续,物理上可以是不连续的内存空间 |
堆是垃圾收集器进行GC的最重要的内存区域
内部结构
Java 堆可以分为:新生代(Eden区、S0区、S1区)和 老年代。
在绝大多数情况下,对象首先分配在eden区
在一次新生代GC回收后,如果对象还存活,则会进入S0或S1
之后,每经历过一次新生代回收,对象如果存活,它的年龄就会加一。
当对象的年龄达到一定条件后,就会被认为是老年代对象,从而进入老年代。
新生代
存放新创建的对象, GC 频繁但快速
Eden 区:新对象首先分配在这里
Survivor 区:两个大小相等的区域 S0、S1,用于存放 Eden 区 GC 后存活的对象
老年代
存放长期存活的对象或大对象,如大数组
GC 频率低,但耗时长
方法区
是 JVM 规范,所有虚拟机必须遵守
线程共享
逻辑上属于堆,但不要求物理连续,也不一定在堆内
垃圾回收行为不确定,规范未强制要求 GC 方法区,但主流 JVM 会回收
内存不足会 OOM
方法区是 JVM 规范定义的概念,永久代/元空间都是其具体实现
保存内容:
类的元数据,参考类加载流程中的加载部分。
运行时常量池 .class 文件中常量池的运行表示,如字面量、符号引用等
字段和方法数据
静态变量 类的 static 字段。JDK 7后,其本身在堆中,但其引用或元信息仍在方法区
JIT 编译后的代码缓存 热点方法被 JIT 编译为本地代码后缓存于此
实现
永久代
JDK 7 以及之前使用永久代,位于堆内,受
-Xmx限制。容易因动态生成类太多而 OOM。GC 不会在主程序运行期间对永久区域进行清理,Full GC 才会清理,因此容易因动态生成类太多而产生 OOM元空间 metasapce
不再位于堆中,而是使用操作系统本地内存。默认只受系统可用内存限制,降低 OOM 风险。但如果不指定最大大小,可能耗尽系统所有可用内存。
元空间的优势:
不再位于 Java 堆,而是使用 操作系统本地内存(Native Memory)。
默认只受系统可用内存限制,大幅降低 OOM 风险。
类的元数据按类加载器隔离管理,卸载更高效。
支持更灵活的内存管理(如按类加载器释放)。
虚拟机栈与本地方法栈
存储 方法执行时的栈帧(Stack Frame),包括局部变量表、操作数栈、动态链接、方法返回地址等。
线程私有,生命周期与线程相同。由于私有,因此栈中的数据是线程安全的,无需同步。
栈帧是核心单位。每调用一个方法,就会创建一个栈帧并压入栈顶。方法执行完毕后,栈帧出栈。
局部变量表
储存 方法参数 和方法内部的 局部变量 。大小在编译器确定。
操作数栈
用于保存中间计算结果,作为字节码指令的操作数。
例如:执行 i = a + b 时:
- 先将
a、b压入操作数栈 - 执行
iadd指令,弹出两个值相加,结果再压入栈 - 最后通过
istore存入局部变量表
局部变量表和操作数栈都是栈帧中的 逻辑结构
动态链接(常量池指针)
指向运行时常量池中该方法所属类的引用,用于支持方法调用过程中的符号解析
方法返回地址
当前方法执行完后,应该返回到调用者的哪条指令继续执行。正常返回或异常退出都会使用此信息。
本地方法栈
为 native 方法服务,与虚拟机栈类似。
| 对比项 | 虚拟机栈 | 本地方法栈 |
|---|---|---|
| 服务对象 | Java 方法 | Native 方法(C/C++ 编写) |
| 规范要求 | 必须支持 | 可自由实现(有些 JVM 合并两者) |
| 异常类型 | StackOverflowError 等 | 类似,但取决于 native 实现 |
程序计数器
记录当前线程正在执行的字节码指令地址。如果是 native 方法,为 undefined.
线程私有;不会发生 OOM ; 字节码解释器通过改变 PC 值来选取下一条指令。