返回首页面试题

面试必备:JVM 内存模型(JMM)详解

2026年03月25日8 min read

面试必备:JVM 内存模型(JMM)详解

JVM 内存区域全景图

┌─────────────────────────────────────────────────────────────────┐
│                         JVM 进程                                 │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                     Method Area                          │   │
│  │   (方法区/元空间)                                         │   │
│  │   • 类信息                                               │   │
│  │   • 运行时常量池                                          │   │
│  │   • 静态变量                                              │   │
│  │   • JIT 编译后的代码                                      │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌───────────────────┐           ┌─────────────────────────┐  │
│  │   Heap (堆)        │           │   Native Method Stack    │  │
│  │   • 对象实例       │           │   (本地方法栈)            │  │
│  │   • 数组          │           │   • native 方法          │  │
│  │                   │           │                          │  │
│  │  ┌─────────────┐  │           └─────────────────────────┘  │
│  │  │ Eden        │  │                                       │
│  │  │ Survivor S0 │  │           ┌─────────────────────────┐  │
│  │  │ Survivor S1 │  │           │    PC Register          │  │
│  │  │   Old Gen   │  │           │    (程序计数器)          │  │
│  │  └─────────────┘  │           │    当前执行的行号          │  │
│  └───────────────────┘           └─────────────────────────┘  │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                     JVM Stack                            │   │
│  │                     (虚拟机栈)                            │   │
│  │   ┌──────────┐  ┌──────────┐  ┌──────────┐             │   │
│  │   │ Thread 1 │  │ Thread 2 │  │ Thread 3 │             │   │
│  │   │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │             │   │
│  │   │ │Frame │ │  │ │Frame │ │  │ │Frame │ │             │   │
│  │   │ │      │ │  │ │      │ │  │ │      │ │             │   │
│  │   │ │局部变量│ │  │ │局部变量│ │  │ │局部变量│ │             │   │
│  │   │ │操作数栈│ │  │ │操作数栈│ │  │ │操作数栈│ │             │   │
│  │   │ │动态链接│ │  │ │动态链接│ │  │ │动态链接│ │             │   │
│  │   │ └──────┘ │  │ └──────┘ │  │ └──────┘ │             │   │
│  │   └──────────┘  └──────────┘  └──────────┘             │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

线程私有区域(每个线程独享)

1. 程序计数器(PC Register)

当前执行的字节码行号

作用:记录当前线程执行到哪一行字节码

特点

  • 线程私有
  • 唯一不会 OutOfMemoryError 的区域
  • 如果执行 native 方法,计数器为空

2. 虚拟机栈(JVM Stack)

public void methodA() {
    int a = 1;           // 栈帧1 入栈
    methodB();           // 栈帧2 入栈
}

public int methodB() {
    int b = 2;           // 执行中
    return b;
}

栈帧结构

┌─────────────────────┐
│      栈帧           │
├─────────────────────┤
│ 局部变量表          │  // local variables
│  • 参数            │
│  • 局部变量         │
├─────────────────────┤
│ 操作数栈            │  // operand stack
│  • 临时操作空间     │
├─────────────────────┤
│ 动态链接            │  // dynamic linking
│  • 指向常量池       │
├─────────────────────┤
│ 返回地址            │  // return address
│  • 方法出口         │
└─────────────────────┘

异常

  • StackOverflowError:栈深度过深(常见递归没终止)
  • OutOfMemoryError:栈内存不够(可以动态扩展时)

3. 本地方法栈(Native Method Stack)

和虚拟机栈类似,但服务于 native 方法(C/C++ 实现)。


线程共享区域

4. 堆(Heap)

核心区域,几乎所有对象实例和数组都存在这里。

// 堆中分配
Object obj = new Object();  // 对象实例在堆中

堆内存划分(Java 8+)

┌──────────────────────────────────────────────┐
│                    堆内存                     │
│                                              │
│  ┌────────────────┐   ┌──────────────────┐  │
│  │   Young Gen     │   │   Old Gen        │  │
│  │   (新生代)       │   │   (老年代)        │  │
│  │                │   │                  │  │
│  │  ┌──────────┐  │   │                  │  │
│  │  │   Eden    │  │   │                  │  │
│  │  │          │  │   │                  │  │
│  │  ├──────────┤  │   │                  │  │
│  │  │ Survivor │  │   │                  │  │
│  │  │    S0    │  │   │                  │  │
│  │  ├──────────┤  │   │                  │  │
│  │  │ Survivor │  │   │                  │  │
│  │  │    S1    │  │   │                  │  │
│  │  └──────────┘  │   │                  │  │
│  │                │   │                  │  │
│  │  比例:8:1:1   │   │     比例:2:1     │  │
│  └────────────────┘   └──────────────────┘  │
│                                              │
│  ┌──────────────────────────────────────────┐│
│  │   Metaspace (元空间)                     ││
│  │   • 类信息                               ││
│  │   • 方法字节码                           ││
│  └──────────────────────────────────────────┘│
└──────────────────────────────────────────────┘
区域比例说明
Eden80%新对象分配
Survivor S010%存活对象交换
Survivor S110%存活对象交换
Old Gen2/3长寿对象、大对象
Metaspace-类信息(不在堆中)

5. 方法区(Method Area)

存储类信息、运行时常量池、静态变量、JIT 编译后的代码。

JDK 7:方法区在堆中(PermGen 永久代)

JDK 8+:移出堆外(Metaspace 元空间)

// 方法区存储
public class User {
    static int count = 0;        // 静态变量 → 方法区
    static final int MAX = 100;  // 常量 → 运行时常量池
    
    public void say() { }        // 方法字节码
}

对象创建流程

new Object()
    │
    ▼
① 类加载检查
    │
    ▼
② 分配内存
    │
    ├── 指针碰撞(内存规整)
    │   └─ 适用于 Serial、ParNew 等收集器
    │
    └── 空闲列表(内存碎片)
        └─ 适用于 CMS 等收集器
    │
    ▼
③ 初始化零值
    │
    ▼
④ 设置对象头
    │
    ▼
⑤ 执行构造函数

常见内存溢出

1. 堆溢出(OutOfMemoryError: Java heap space)

// 创建大量对象不释放
List<byte[]> list = new ArrayList<>();
while (true) {
    list.add(new byte[1024 * 1024]);  // 不断添加 1MB
}

2. 栈溢出(StackOverflowError)

// 无限递归
public void recursion() {
    recursion();  // 没有终止条件
}

3. 方法区溢出

// 动态生成大量类
// 使用 CGLIB 或反射不断创建类

4. 元空间溢出(Metaspace)

// JDK 8+,类加载过多
// 常见于框架动态代理、字节码生成

内存参数配置

# 堆大小配置
-Xms256m          # 初始堆大小
-Xmx512m          # 最大堆大小
-Xmn128m          # 新生代大小
-XX:MetaspaceSize=128m   # 元空间初始大小
-XX:MaxMetaspaceSize=256m

# 打印 GC 日志
-XX:+PrintGCDetails
-Xloggc:gc.log

# OOM 时导出堆 dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof

面试高频问题

Q1:什么情况下对象会进入老年代?

  1. 年龄达到阈值(默认 15,CMS 是 6)
  2. 大对象直接分配(超过 Eden 区)
  3. 动态年龄判断:Survivor 中相同年龄所有对象之和 > Survivor 的 50%

Q2:为什么需要两个 Survivor 区?

只有一个 Survivor 区会产生内存碎片。两个 Survivor(S0、S1)交替使用,避免碎片化,方便复制算法。

Q3:方法区和堆的区别?

  • :对象实例、数组,线程共享
  • 方法区:类信息、静态变量、常量池,线程共享
  • JDK 8+:方法区移到元空间,不在堆中

总结

区域线程私有/共享异常
程序计数器私有
虚拟机栈私有SOE, OOM
本地方法栈私有SOE, OOM
共享OOM
元空间共享OOM

核心理解:堆是对象的大本营,栈是方法的执行轨迹,方法区是类的档案室。

评论区