Skip to content

内存分布

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 及之后:运行时常量池移至堆,方法区移至元空间。

如需更深入的某部分内容(如堆的分代结构或元空间的分配流程),请告诉我!