JVM 内存结构,常见可以分成 运行时数据区 这几部分:
1. 程序计数器(Program Counter Register)
- 每个线程私有
- 记录当前线程正在执行的字节码指令地址
- 线程切换后,能恢复到正确执行位置
- 是 JVM 规范里唯一一个不会发生 OutOfMemoryError 的区域
2. Java 虚拟机栈(JVM Stack)
-
每个线程私有
-
方法调用时会创建一个栈帧
-
栈帧里主要有:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口信息
-
方法执行结束,栈帧出栈
-
可能出现:
StackOverflowErrorOutOfMemoryError
3. 本地方法栈(Native Method Stack)
- 每个线程私有
- 为 JVM 调用 native 方法 服务
- 和虚拟机栈类似,只不过服务对象是本地方法
4. Java 堆(Heap)
-
线程共享
-
JVM 中最大的一块内存区域
-
几乎所有对象实例和数组都在这里分配
-
是垃圾回收器(GC)管理的主要区域
-
常见进一步划分:
-
新生代
- Eden
- Survivor(From / To)
-
老年代
-
-
可能出现:
OutOfMemoryError: Java heap space
5. 方法区(Method Area)
-
线程共享
-
存储类相关信息:
- 类元数据
- 运行时常量池
- 字段信息
- 方法信息
- 静态变量
- 即时编译后的代码缓存等
-
JDK 1.8 以后,HotSpot 用 元空间(Metaspace) 实现方法区,使用本地内存,不再是永久代
-
可能出现:
OutOfMemoryError: Metaspace
6. 运行时常量池(Runtime Constant Pool)
-
方法区的一部分
-
存放编译期生成的各种字面量和符号引用
-
类加载后进入运行时常量池
-
比如:
- 字符串字面量
- 类和方法的符号引用
一张简单总结表
| 区域 | 线程私有/共享 | 作用 |
|---|---|---|
| 程序计数器 | 私有 | 记录当前执行位置 |
| 虚拟机栈 | 私有 | 方法调用和局部变量 |
| 本地方法栈 | 私有 | native 方法调用 |
| 堆 | 共享 | 对象实例和数组 |
| 方法区 | 共享 | 类信息、常量、静态变量、JIT代码 |
| 运行时常量池 | 共享 | 常量和符号引用 |
面试里经常追问的点
JDK 1.7 / 1.8 相关
- 永久代(PermGen) :JDK 1.7 及以前 HotSpot 对方法区的一种实现
- 元空间(Metaspace) :JDK 1.8 开始替代永久代,使用本地内存
堆和栈的区别
- 栈:线程私有,存方法执行过程中的局部变量,生命周期随方法
- 堆:线程共享,存对象实例,生命周期由 GC 管理
为什么要有程序计数器
- 因为 Java 支持多线程,线程切换后需要知道上次执行到哪里
背诵版
JVM 内存结构主要包括:程序计数器、虚拟机栈、本地方法栈、堆、方法区,其中运行时常量池属于方法区的一部分。程序计数器、虚拟机栈、本地方法栈是线程私有的;堆和方法区是线程共享的。堆主要存对象实例,是 GC 的主要区域;栈主要管理方法调用和局部变量;方法区主要存类信息、常量、静态变量和 JIT 编译代码。JDK 1.8 以后,方法区由元空间实现,替代了永久代。