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 起:转移至堆中
  • 目的:避免重复创建相同字符串,提升效率与节省内存

示例
java 复制代码
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 编译代码等