简述一下 JVM 的内存模型
JVM 内存模型(Java Virtual Machine Memory Model)是JVM 规范中定义的内存划分规则,用于管理 Java 程序运行时的内存,确保内存分配、使用和回收的有序性。不同 JDK 版本的内存模型略有差异(如 JDK 8 移除永久代,引入元空间),当前主流(JDK 8+)的内存模型如下:
# 1. 内存模型的核心区域(按线程可见性划分)
JVM 内存分为 “线程私有区域”(每个线程独立拥有,生命周期与线程一致)和 “线程共享区域”(所有线程共享,生命周期与 JVM 一致),具体划分:
# (1)线程私有区域(Thread-Private Area)
每个线程启动时创建,线程结束时销毁,无需 GC 回收(除栈帧中的对象引用指向堆内存):
- 1. 程序计数器(Program Counter Register):
- 作用:记录当前线程执行的字节码指令的地址(行号),用于线程切换后恢复执行位置(如线程 A 被暂停,切换到线程 B,线程 A 再次执行时,通过程序计数器找到上次执行的指令);
- 特点:
- 线程私有,每个线程有独立的程序计数器;
- 唯一不会抛出
OutOfMemoryError的区域(内存大小固定,与 CPU 核心数匹配); - 若线程执行的是本地方法(JNI 方法),程序计数器值为
undefined(本地方法由 C/C++ 执行,无需字节码指令地址)。
- 2. 虚拟机栈(VM Stack):
- 作用:存储线程执行方法时的 “栈帧”(Stack Frame),每个方法调用对应一个栈帧入栈,方法执行完成对应栈帧出栈;
- 栈帧组成:
- 局部变量表:存储方法的局部变量(基本数据类型、对象引用),内存大小在编译时确定;
- 操作数栈:用于方法执行时的临时数据运算(如
i + j,先将i和j压入操作数栈,再执行加法指令); - 动态链接:指向方法区中该方法的类元信息(如方法的符号引用,用于动态绑定,如多态);
- 方法返回地址:方法执行完成后,返回的父方法的指令地址(如方法 A 调用方法 B,方法 B 执行完后,通过返回地址回到方法 A 的下一条指令);
- 特点:
- 线程私有,栈深度由方法调用链长度决定;
- 内存大小可固定(
-XX:ThreadStackSize,默认 1MB)或动态扩展; - 异常:若栈深度超过最大限制,抛出
StackOverflowError(如递归调用无终止条件);若栈内存无法动态扩展(如内存不足),抛出OutOfMemoryError。
- 3. 本地方法栈(Native Method Stack):
- 作用:与虚拟机栈类似,区别是存储线程执行 “本地方法”(JNI 方法)时的栈帧;
- 特点:
- 线程私有,由 JVM 实现(如 HotSpot JVM 将本地方法栈与虚拟机栈合并,统一管理);
- 异常:与虚拟机栈一致,抛出
StackOverflowError或OutOfMemoryError。
# (2)线程共享区域(Thread-Shared Area)
所有线程共享,由 JVM 启动时创建,JVM 关闭时销毁,需 GC 回收(堆内存和方法区的部分数据):
1. 堆(Heap):
- 作用:存储 Java 对象实例(
new关键字创建的对象)和数组,是 JVM 内存中最大的区域,也是 GC 的主要区域; - 分代划分(基于 “分代回收” 思想):
- 新生代(Young Generation,占堆内存 1/3):
- Eden 区(80%):新对象优先分配到 Eden 区;
- Survivor 区(20%):分为 From Survivor 和 To Survivor(各 10%),存储 Eden 区 GC 后存活的对象;
- 老年代(Old Generation,占堆内存 2/3):存储从新生代晋升的长期存活对象(如年龄达到
MaxTenuringThreshold的对象);
- 新生代(Young Generation,占堆内存 1/3):
- 特点:
- 线程共享,所有线程可访问堆中的对象;
- 内存大小可通过
-Xms(初始堆大小)和-Xmx(最大堆大小)配置(推荐设置为相同值,避免频繁扩容); - 异常:若堆内存无法分配新对象(且 GC 无法释放足够内存),抛出
OutOfMemoryError: Java heap space。
- 作用:存储 Java 对象实例(
2. 方法区(Method Area):
- 作用:存储类元信息(类结构、字段、方法、接口定义)、常量池(字符串常量、数字常量)、静态变量、即时编译器(JIT)编译后的代码等;
- JDK 版本差异:
- JDK 7 及之前:方法区被称为 “永久代(PermGen)”,属于堆内存的一部分,大小通过
-XX:PermSize和-XX:MaxPermSize配置; - JDK 8 及之后:永久代被元空间(Metaspace) 替代,元空间使用本地内存(Native Memory),大小默认无上限(可通过
-XX:MetaspaceSize和-XX:MaxMetaspaceSize限制);
- JDK 7 及之前:方法区被称为 “永久代(PermGen)”,属于堆内存的一部分,大小通过
- 特点:
- 线程共享,所有线程可访问类元信息;
- 内存回收效率低:仅当类的类加载器被回收且无对象引用该类时,类元信息才会被回收;
- 异常:JDK 7 及之前,永久代满时抛出
OutOfMemoryError: PermGen Space;JDK 8 及之后,元空间满时抛出OutOfMemoryError: Metaspace。
3. 运行时常量池(Runtime Constant Pool):
- 作用:方法区的一部分,存储类编译后的常量(如字符串常量
"abc"、整数常量123)、符号引用(类名、方法名的字符串引用); - 特点:
- 动态性:JDK 7 后,运行时常量池支持动态添加常量(如
String.intern()方法,可将字符串动态加入常量池); - 内存回收:当常量不再被引用时,可被 GC 回收(如动态添加的字符串常量)。
- 动态性:JDK 7 后,运行时常量池支持动态添加常量(如
- 作用:方法区的一部分,存储类编译后的常量(如字符串常量
上次更新: 12/30/2025