Skip to main content
JVM 在执行 Java 程序时,会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建和销毁时间。

运行时数据区

┌─────────────────────────────────────────────────────────────┐
│                    JVM 运行时数据区                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    线程共享区域                       │   │
│  │  ┌─────────────────┐  ┌─────────────────────────┐  │   │
│  │  │       堆        │  │       方法区             │  │   │
│  │  │    (Heap)       │  │   (Method Area)         │  │   │
│  │  └─────────────────┘  └─────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    线程私有区域                       │   │
│  │  ┌─────────┐  ┌─────────────┐  ┌─────────────────┐ │   │
│  │  │程序计数器│  │  虚拟机栈   │  │   本地方法栈    │ │   │
│  │  │(PC Reg) │  │ (VM Stack) │  │(Native Stack) │ │   │
│  │  └─────────┘  └─────────────┘  └─────────────────┘ │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

程序计数器(PC Register)

程序计数器是一块较小的内存空间,用于存储当前线程正在执行的字节码指令地址。

特点

  • 线程私有:每个线程都有自己的程序计数器
  • 唯一不会 OOM:不会发生 OutOfMemoryError
  • 执行 Native 方法时为空:此时计数器值为 undefined

作用

public int add(int a, int b) {
    int c = a + b;    // 程序计数器记录当前执行到这里
    return c;         // 然后更新为这里的地址
}

虚拟机栈(VM Stack)

虚拟机栈是 Java 方法执行的内存模型,每个方法执行时都会创建一个栈帧。

栈帧结构

┌─────────────────────────────────────┐
│              栈帧 (Stack Frame)      │
├─────────────────────────────────────┤
│  ┌─────────────────────────────┐   │
│  │      局部变量表               │   │ ← 存储方法参数和局部变量
│  │   (Local Variables)         │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │       操作数栈                │   │ ← 计算过程的临时存储
│  │   (Operand Stack)           │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │       动态链接                │   │ ← 指向运行时常量池的引用
│  │   (Dynamic Linking)         │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │       方法返回地址            │   │ ← 方法返回后继续执行的位置
│  │   (Return Address)          │   │
│  └─────────────────────────────┘   │
└─────────────────────────────────────┘

局部变量表

存储方法参数和局部变量,以 Slot 为单位。
数据类型Slot 数量
boolean, byte, char, short, int, float1
long, double2
reference1
public void method(int a, long b) {
    double c = 3.14;
    // 局部变量表:
    // Slot 0: this(非静态方法)
    // Slot 1: a (int)
    // Slot 2-3: b (long)
    // Slot 4-5: c (double)
}

操作数栈

用于计算过程中的临时存储。
int a = 1;
int b = 2;
int c = a + b;

// 字节码执行过程:
// iconst_1    → 操作数栈:[1]
// istore_1    → 操作数栈:[],局部变量表:a=1
// iconst_2    → 操作数栈:[2]
// istore_2    → 操作数栈:[],局部变量表:b=2
// iload_1     → 操作数栈:[1]
// iload_2     → 操作数栈:[1, 2]
// iadd        → 操作数栈:[3]
// istore_3    → 操作数栈:[],局部变量表:c=3

栈溢出

// StackOverflowError 示例
public void recursion() {
    recursion();  // 无限递归,栈帧不断压入
}

相关参数

# 设置栈大小(默认 1MB)
-Xss256k
-Xss1m

本地方法栈(Native Method Stack)

与虚拟机栈类似,但用于执行 Native 方法(C/C++ 实现)。
// native 方法
public native void nativeMethod();

堆(Heap)

堆是 JVM 管理的最大内存区域,用于存放对象实例。

堆内存结构(JDK 8)

┌─────────────────────────────────────────────────────────────┐
│                          堆 (Heap)                          │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────────────────────────────────────────────────┐  │
│  │                    年轻代 (Young Gen)                  │  │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────────┐  │  │
│  │  │   Eden     │  │ Survivor0  │  │   Survivor1    │  │  │
│  │  │   (80%)    │  │   (10%)    │  │     (10%)      │  │  │
│  │  └────────────┘  └────────────┘  └────────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                    老年代 (Old Gen)                    │  │
│  │                                                       │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

对象分配过程

1. 新对象优先在 Eden 区分配

2. Eden 区满 → Minor GC

3. 存活对象 → Survivor 区(年龄 +1)

4. 年龄达到阈值(默认 15)→ 老年代

5. 老年代满 → Major GC / Full GC

堆内存参数

# 初始堆大小
-Xms512m

# 最大堆大小
-Xmx1024m

# 年轻代大小
-Xmn256m

# Eden 与 Survivor 比例(默认 8:1:1)
-XX:SurvivorRatio=8

# 年轻代与老年代比例
-XX:NewRatio=2

OutOfMemoryError

// 堆内存溢出示例
public class HeapOOM {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[1024 * 1024]);  // 不断创建 1MB 对象
        }
    }
}
// java.lang.OutOfMemoryError: Java heap space

方法区(Method Area)

方法区用于存储类信息、常量、静态变量、即时编译器编译后的代码等。

JDK 版本变化

版本实现存储位置
JDK 7 及之前永久代(PermGen)堆内存
JDK 8 及之后元空间(Metaspace)本地内存

方法区内容

┌─────────────────────────────────────────────────────────────┐
│                      方法区 / 元空间                         │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │    类信息        │  │         运行时常量池             │  │
│  │ - 类名、访问修饰符│  │ - 字面量(字符串、数字)         │  │
│  │ - 字段信息       │  │ - 符号引用                      │  │
│  │ - 方法信息       │  │                                 │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │    静态变量      │  │         JIT 编译代码             │  │
│  │                 │  │                                 │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

运行时常量池

public class ConstantPoolDemo {
    // 字面量
    private String str = "hello";
    private int num = 100;
    
    // 符号引用
    public void method() {
        System.out.println(str);  // 方法引用
    }
}

元空间参数

# 初始元空间大小
-XX:MetaspaceSize=128m

# 最大元空间大小(默认无限制)
-XX:MaxMetaspaceSize=256m

元空间溢出

// 元空间溢出示例(动态生成大量类)
public class MetaspaceOOM {
    public static void main(String[] args) {
        while (true) {
            // 使用 CGLib 动态生成类
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Object.class);
            enhancer.setCallback((MethodInterceptor) (o, m, a, p) -> p.invokeSuper(o, a));
            enhancer.create();
        }
    }
}
// java.lang.OutOfMemoryError: Metaspace

直接内存(Direct Memory)

直接内存不属于 JVM 运行时数据区,但也被频繁使用。

特点

  • 不受 JVM 堆大小限制
  • 减少数据拷贝,IO 性能更好
  • 分配和回收成本较高

使用场景

// NIO 使用直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);

参数配置

# 设置直接内存大小
-XX:MaxDirectMemorySize=256m

内存区域总结

区域线程作用异常
程序计数器私有记录执行位置
虚拟机栈私有方法执行StackOverflowError, OOM
本地方法栈私有Native 方法执行StackOverflowError, OOM
共享对象实例OutOfMemoryError
方法区共享类信息、常量OutOfMemoryError

小结

  • 程序计数器:线程私有,记录执行位置,唯一不会 OOM
  • 虚拟机栈:线程私有,方法执行的栈帧
  • :线程共享,对象实例存储,GC 主要区域
  • 方法区:线程共享,类信息和常量池,JDK 8 后为元空间
  • 理解内存结构是理解 GC 和调优的基础