JVM 内存模型详解

JVM(Java Virtual Machine)是 Java 程序运行的基石,理解它的内存模型是排查 OOM、调优 GC 的前提。

一、运行时数据区

JVM 在运行程序时会把内存划分为以下几个区域:

┌─────────────────────────────────────────────┐
│              JVM 运行时数据区                 │
├──────────────┬──────────────────────────────┤
│  线程私有     │          线程共享              │
├──────────────┼──────────────────────────────┤
│ 程序计数器    │                              │
│ 虚拟机栈      │           堆 (Heap)          │
│ 本地方法栈    │        方法区 (元空间)         │
└──────────────┴──────────────────────────────┘

1. 程序计数器(PC Register)

一块较小的内存空间,记录当前线程正在执行的字节码行号

  • 线程私有
  • 唯一不会发生 OOM 的区域

2. 虚拟机栈(VM Stack)

描述 Java 方法执行的内存模型。每个方法执行时都会创建一个栈帧

public int add(int a, int b) {
    int sum = a + b;
    return sum;
}

栈帧包含:

  • 局部变量表:存放方法参数和方法内定义的变量
  • 操作数栈:存放计算过程中的中间结果
  • 动态链接:指向运行时常量池的方法引用
  • 方法出口:方法返回的地址

3. 本地方法栈

为 Native 方法服务的栈区,与虚拟机栈作用类似。

4. 堆(Heap)

所有线程共享,存放对象实例,是 GC 的主战场。

// new 出来的对象都在堆中
User user = new User("小宇宙", 18);

现代 JVM 将堆划分为:

  • 新生代:Eden + Survivor 0 + Survivor 1(8:1:1)
  • 老年代:长期存活的对象

5. 方法区(元空间)

存储类信息、常量、静态变量、JIT 编译后的代码

JDK 8 之后,方法区的实现从永久代(PermGen)改为元空间(Metaspace),使用本地内存,避免了永久代 OOM 的问题。

二、对象创建过程

当执行 new User("小宇宙", 18) 时,JVM 内部发生的事情:

  1. 类加载检查:检查 User 类是否已加载、解析、初始化
  2. 分配内存:在堆中划出固定大小的内存

- 指针碰撞(内存规整时)

- 空闲列表(内存碎片时)

  1. 内存空间初始化:清零操作
  2. 对象头设置:设置哈希码、GC 分代年龄、锁状态
  3. 执行 <init> 方法:调用构造函数

三、GC 工作原理

判断对象是否存活

  • 引用计数法(已淘汰):循环引用问题
  • 可达性分析(主流):从 GC Roots 出发是否能到达

GC Roots 包括:

  • 虚拟机栈中的局部变量
  • 方法区的类静态属性
  • 方法区的常量
  • 本地方法栈的 JNI 引用

垃圾收集算法

| 算法 | 描述 | 适用场景 |

| ---- | ---- | ---- |

| 标记-清除 | 标记存活,清除其他 | 老年代 |

| 标记-整理 | 清除后整理碎片 | 老年代 |

| 复制算法 | 两块区域来回复制 | 新生代 |

| 分代收集 | 综合以上算法 | 现代 JVM |

四、实战:OOM 排查

// 无限创建对象,触发堆 OOM
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]); // 1MB
}

报错java.lang.OutOfMemoryError: Java heap space

排查思路

  1. 添加 JVM 参数:-XX:+HeapDumpOnOutOfMemoryError
  2. 使用 MAT / VisualVM 分析堆转储文件
  3. 定位大对象,优化代码逻辑

五、小结

| 区域 | 私有/共享 | 存储内容 | 异常 |

| ---- | ---- | ---- | ---- |

| 程序计数器 | 私有 | 字节码行号 | 不会 OOM |

| 虚拟机栈 | 私有 | 栈帧 | StackOverflowError / OOM |

| 本地方法栈 | 私有 | Native 方法 | OOM |

| 堆 | 共享 | 对象实例 | OOM |

| 方法区 | 共享 | 类信息、常量 | OOM |

小宇宙提示:掌握 JVM 内存模型,是写出高性能、高可用 Java 应用的前提。