内存分布¶
Java 虚拟机(JVM)的运行时数据区域主要分为以下几个部分,每个部分存放的内容不同
1. 程序计数器(Program Counter Register)¶
- 内容:存放当前线程执行的字节码指令地址(即下一条将要执行的指令的地址)。
- 特点:
- 每个线程私有,互不干扰。
- 如果执行的是 Java 方法,计数器记录正在执行的字节码指令地址;如果是本地(native)方法,则计数器值为空(undefined)。
- 占用内存较小,不会发生 OutOfMemoryError。
2. 虚拟机栈(JVM Stack) #栈¶
- 内容:存储线程执行方法时的栈帧(Stack Frame)。每个栈帧包含:
- 局部变量表:存放方法参数和局部变量(包括基本数据类型、对象引用)。
- 操作数栈:用于计算的临时数据存储。
- 动态链接:将符号引用转换为直接引用。
- 方法返回地址:记录方法执行完成后的返回点。
- 特点:
- 线程私有,随线程创建和销毁。
- 可能抛出 StackOverflowError(栈深度溢出)或 OutOfMemoryError(栈空间不足)。
3. 本地方法栈(Native Method Stack) #栈¶
- 内容:与虚拟机栈类似,但专为本地(native)方法服务,存储本地方法执行时的栈帧。
- 特点:
- 线程私有。
- 某些 JVM 实现(如 HotSpot)可能将本地方法栈与虚拟机栈合并。
- 可能抛出 StackOverflowError 或 OutOfMemoryError。
4. 堆(Heap) #堆¶
- 内容:
- 存储所有通过
new关键字创建的对象实例和数组。 - 自 JDK 1.7 起,运行时常量池(字符串常量池等)也移到堆中。
- 特点:
- 所有线程共享,是 JVM 内存中最大的一块区域。
- 可通过
-Xms(初始堆大小)和-Xmx(最大堆大小)设置。 - 是垃圾回收(GC)的主要区域,可能抛出 OutOfMemoryError(堆空间不足)。
- 堆通常分为新生代(Young Generation)和老年代(Old Generation),新生代又包括 Eden 区和 Survivor 区。
5. 方法区(Method Area)¶
- 内容:
- 存储已被 JVM 加载的类信息(如类结构、字段、方法信息)。
- 静态变量(static 变量)。
- 常量(包括 final 修饰的常量)。
- 即时编译器(JIT)编译后的代码。
- 特点:
- 所有线程共享。
- 在 JDK 1.7 及之前,HotSpot JVM 将方法区实现为“永久代”(PermGen),可能抛出 OutOfMemoryError(PermGen 空间不足)。
- 自 JDK 1.8 起,永久代被移除,方法区移至元空间(Metaspace),使用本地内存(native memory),通过
-XX:MaxMetaspaceSize设置上限。 - 运行时常量池在 JDK 1.7 后移至堆中,但方法区仍包含其他常量信息。
6. 运行时常量池(Runtime Constant Pool)¶
- 内容:
- 存储类文件中常量池表的运行时表示,包括:
- 字面量(literal,如字符串字面量、数字常量)。
- 符号引用(symbolic references,如类、方法、字段的引用)。
- 特点:
- 每个类或接口独有一份,属于方法区的一部分(JDK 1.7 之前)或堆的一部分(JDK 1.7 及之后)。
- 可能抛出 OutOfMemoryError(常量池空间不足)。
总结表¶
| 区域 | 存放内容 | 线程共享 | 异常情况 |
|---|---|---|---|
| 程序计数器 | 字节码指令地址 | 私有 | 无 |
| 虚拟机栈 | 栈帧(局部变量表、操作数栈等) | 私有 | StackOverflowError, OutOfMemoryError |
| 本地方法栈 | 本地方法栈帧 | 私有 | StackOverflowError, OutOfMemoryError |
| 堆 | 对象实例、数组、运行时常量池(JDK 1.7 起) | 共享 | OutOfMemoryError |
| 方法区/元空间 | 类信息、静态变量、常量、JIT 编译代码 | 共享 | OutOfMemoryError(元空间不足) |
| 运行时常量池 | 字面量、符号引用 | 共享 | OutOfMemoryError |
注意事项¶
- 线程私有 vs 共享:程序计数器、虚拟机栈、本地方法栈是线程私有的,堆和方法区是线程共享的。
- 垃圾回收:堆和方法区(元空间)是垃圾回收的主要区域,虚拟机栈和本地方法栈随方法调用和返回自动回收。
- JDK 版本差异:
- JDK 1.7 及之前:运行时常量池在方法区(永久代)。
- JDK 1.8 及之后:运行时常量池移至堆,方法区移至元空间。