JVM 内存共享区域详解
Java 虚拟机在执行 Java 程序时,会将所管理的内存划分为若干个不同的数据区域。其中,堆(Heap) 和 方法区(Method Area) 是所有线程共享的区域,属于 JVM 的共享内存区。
一、JVM 内存结构总览
区域名称 | 是否线程共享 | 说明 |
---|---|---|
程序计数器(Program Counter Register) | 否 | 当前线程执行字节码的行号指示器 |
Java 虚拟机栈(JVM Stack) | 否 | 每个线程私有,方法调用栈帧 |
本地方法栈(Native Method Stack) | 否 | 用于调用 Native 方法 |
Java 堆(Heap) | 是 | 所有线程共享,用于存放对象实例 |
方法区(Method Area) | 是 | 所有线程共享,存储类结构、静态变量、运行时常量池等 |
运行时常量池(Runtime Constant Pool) | 是 | 方法区的一部分,存储编译时生成的常量和符号引用 |
直接内存(Direct Memory) | 特殊 | 不在堆中,使用 JNI 分配的本地内存 |
二、JVM 共享内存区域有哪些?
1. Java 堆(Heap)
-
作用:Java 堆是 JVM 中最大的一块内存区域,用于存储对象实例和数组,是垃圾收集器管理的主要区域。
-
线程共享:是
-
创建时机:在 JVM 启动时创建。
-
内存划分:
-
新生代(Young Generation):
- Eden 区:新对象优先在这里分配。
- Survivor 区(S0 和 S1):用作对象复制回收。
-
老年代(Old Generation):
- 存放生命周期较长的对象。
-
(JDK 1.7 以前)永久代(PermGen)
-
(JDK 1.8 以后)元空间(Metaspace):使用本地内存替代永久代。
-
新生代对象分配与晋升过程
- 对象先分配到 Eden 区。
- 经一次 Minor GC 后存活的对象转移至 Survivor 区。
- 如果在 Survivor 区多次存活(年龄达阈值如 15),将晋升到老年代。
2. 方法区(Method Area)→ 元空间(Metaspace)
-
作用:用于存储已被虚拟机加载的类信息、常量、静态变量、JIT 编译后的代码等。
-
线程共享: 是
-
JDK 变化:
- JDK 1.7 之前方法区由永久代实现。
- JDK 1.8 开始使用 元空间(Metaspace) 替代,分配在本地内存中。
方法区存储内容包括:
- 类结构信息(字段、方法、访问修饰符等)
- 运行时常量池(Runtime Constant Pool)
- 静态变量
- 类初始化信息
- JIT 编译后的代码缓存等
3. 运行时常量池(Runtime Constant Pool)
-
所属区域:方法区的一部分
-
线程共享: 是
-
内容:
- 编译时生成的字面量(如字符串、整型常量等)
- 符号引用(类名、方法名等)
4. 字符串常量池(String Constant Pool)
-
位置:
- JDK 1.6 及以前:在永久代
- JDK 1.7 起:转移至堆中
-
目的:避免重复创建相同字符串,提升效率与节省内存
示例
String a = "ab";
String b = "ab";
System.out.println(a == b); // true,共享常量池引用
字符串常量池为何从永久代迁移到堆?
- 永久代 GC 频率低,字符串长时间不回收。
- 堆空间更大、GC 更灵活,能提升字符串内存回收效率。
5. 直接内存(Direct Memory)
-
作用:通过
ByteBuffer.allocateDirect()
分配的内存,常用于性能敏感的 I/O 操作。 -
使用方式:通过 JNI 方式直接使用本地内存。
-
特点:
- 不属于 JVM 规范定义的内存区域。
- 不是堆内存,也不在方法区。
- 容易出现 OOM 错误(如未手动释放)。
三、JVM 非共享内存区域(仅作对比)
区域 | 线程共享 | 用途 |
---|---|---|
程序计数器 | 否 | 记录当前线程执行的位置(字节码行号) |
虚拟机栈 | 否 | 方法调用、局部变量、操作数栈等 |
本地方法栈 | 否 | 执行 native 方法 |
面试高频考点总结
问题 | 要点 |
---|---|
JVM 哪些区域是线程共享的? | Java 堆、方法区(元空间)、运行时常量池、字符串常量池 |
方法区和堆的区别? | 方法区存储类结构信息,堆存储对象实例;两者都线程共享 |
JDK 1.7/1.8 的内存结构变化? | 移除永久代,引入元空间;字符串常量池移动到堆 |
什么是直接内存? | 不在堆、不在方法区,用 JNI 访问的本地内存 |
方法区中存储的内容有哪些? | 类元数据、静态变量、常量池、JIT 编译代码等 |