树码科技 | 技术博客

JVM 五种内存区域

2026/01/23
84
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)有何影响?