源本科技 | 码上会

JVM 五种内存区域

2026/01/23
55
0

学习目标

  • 掌握 JVM 运行时划分的五种核心内存区域

  • 理解每个区域的用途、生命周期与线程共享特性

  • 能够区分堆、栈、方法区等关键区域的存储内容

  • 了解 Metaspace 对传统方法区的演进意义


JVM 内存区域总览

Java 虚拟机(JVM)在执行 Java 程序时,将内存划分为 五个主要运行时数据区。这些区域协同工作,支撑类加载、对象创建、方法调用和线程执行等核心功能。

内存区域

是否线程共享

主要用途

方法区(Method Area)

存储类元数据、静态变量、常量池等

堆(Heap)

存储所有对象实例和数组

虚拟机栈(Stack)

否(每个线程私有)

存储方法调用帧、局部变量、操作数栈

程序计数器(PC Register)

否(每个线程私有)

记录当前线程执行的字节码指令地址

本地方法栈(Native Method Stack)

否(每个线程私有)

支持 native 方法(如 C/C++)的调用


方法区

核心作用

  • 存储 类级别的结构信息,包括:

    • 类的字节码(Bytecode)

    • 方法和字段的元数据

    • 静态变量(static 字段)

    • 运行时常量池(Runtime Constant Pool)

    • 接口与注解信息

演进:从 PermGen 到 Metaspace

  • Java 7 及之前:方法区由 永久代(PermGen) 实现,位于 Java 堆内。

  • Java 8 起:永久代被移除,改用 Metaspace(元空间),使用 本地内存(Native Memory),不再受 -Xmx 堆大小限制。

优势:避免 java.lang.OutOfMemoryError: PermGen space,提升类加载灵活性。

注意:虽然静态变量本身存储在 Metaspace,但如果其值是对象引用(如 static List list = new ArrayList<>()),对象实例仍分配在堆中,Metaspace 仅保存引用。


核心作用

  • 唯一存放对象实例和数组的地方

  • 所有通过 new 创建的对象均在此分配内存

  • 垃圾回收(GC)的主要区域

特性

  • 线程共享:所有线程均可访问堆中的对象(需同步控制)

  • 动态分配:内存大小可在运行时调整(通过 -Xms / -Xmx

  • 分代管理:通常分为新生代(Young Gen)和老年代(Old Gen)

示例

String s = new String("Hello"); // "Hello" 对象 → 堆;s 引用 → 栈
int[] arr = new int[10];        // 数组对象 → 堆

虚拟机栈

核心作用

  • 支撑 方法调用与返回 的执行模型

  • 每个方法调用对应一个 栈帧(Stack Frame)

栈帧包含

  • 局部变量表(Local Variables)

  • 操作数栈(Operand Stack)

  • 动态链接(指向运行时常量池的方法引用)

  • 方法返回地址

特性

  • 线程私有:每个线程启动时创建独立栈

  • 自动管理:方法执行结束,栈帧自动出栈释放

  • LIFO 结构:后进先出,高效内存管理

局部基本类型变量(如 int, boolean)直接存储在栈中;对象引用也存于栈,但对象本身在堆。


程序计数器

核心作用

  • 记录 当前线程正在执行的 JVM 字节码指令地址

  • 是线程切换后能恢复执行的关键

特性

  • 线程私有:每个线程拥有独立 PC 寄存器

  • 最小内存区域:仅存储一个地址值

  • 唯一不会发生 OOM 的区域

对于 native 方法,PC 值未定义(因不由 JVM 解释执行)。


本地方法栈

核心作用

  • 支持 native 方法(非 Java 编写,如 C/C++)的调用

  • 存储 native 方法的调用状态与参数

特性

  • 线程私有

  • 具体实现由 JVM 厂商决定(可能与 JVM 栈合并)

  • 也称为 “C 栈”

常用于 JNI(Java Native Interface)场景,如调用操作系统 API 或高性能计算库。


关键对比

区域

线程共享

存储内容

是否 GC

是否可能 OOM

方法区

类元数据、静态变量

可选(取决于 JVM)

是(Metaspace 耗尽本地内存)

对象、数组

是(主要区域)

虚拟机栈

局部变量、方法帧

否(自动释放)

是(栈深度过大)

PC 寄存器

指令地址

本地方法栈

native 方法状态


思考题

  1. 为什么程序计数器是唯一不会发生 OutOfMemoryError 的内存区域?

  2. 在多线程环境下,如果多个线程同时访问同一个堆中的对象,JVM 如何保证内存可见性与一致性?

  3. Metaspace 使用本地内存而非 Java 堆,这对系统整体内存管理和容器化部署(如 Docker)有何影响?