文章目录
-
- [1. 全局概览:JVM 内存的五脏六腑](#1. 全局概览:JVM 内存的五脏六腑)
-
- [1.1 内存模型架构图](#1.1 内存模型架构图)
- [2. 线程私有区域:精准控制执行流](#2. 线程私有区域:精准控制执行流)
-
- [2.1 程序计数器 (Program Counter Register)](#2.1 程序计数器 (Program Counter Register))
- [2.2 虚拟机栈 (VM Stack)](#2.2 虚拟机栈 (VM Stack))
- [3. 线程共享区域:海量数据存储](#3. 线程共享区域:海量数据存储)
-
- [3.1 堆 (Heap)](#3.1 堆 (Heap))
- [3.2 方法区 (Method Area) / 元空间 (Metaspace)](#3.2 方法区 (Method Area) / 元空间 (Metaspace))
- [4. 综合视角:一个对象是如何创建的?](#4. 综合视角:一个对象是如何创建的?)
对于 Java 开发者而言,JVM(Java Virtual Machine)内存模型不仅仅是面试中的高频考点,更是解决 OutOfMemoryError (OOM) 和 StackOverflowError 等线上故障的理论基石。
本文将带你深入 JVM 的运行时数据区(Runtime Data Areas),通过图解和代码示例,彻底搞懂 Java 内存的布局与运作。
1. 全局概览:JVM 内存的五脏六腑
JVM 在执行 Java 程序时,会将管理的内存划分为若干个不同的数据区域。这些区域有的随着虚拟机启动而创建,有的则依赖用户线程的启动和结束而建立和销毁。
我们可以将 JVM 内存划分为两大类:线程私有(Thread Local) 和 线程共享(Thread Shared)。
1.1 内存模型架构图
JVM 运行时数据区
🔒 线程私有区域
🧵 线程共享区域
方法区 / Metaspace
(类信息, 常量, 静态变量)
堆内存
(对象实例, 数组)
虚拟机栈
(Java 方法执行)
本地方法栈
(Native 方法执行)
程序计数器
(当前指令地址)
执行引擎
本地库接口
2. 线程私有区域:精准控制执行流
这部分内存区域的生命周期与线程相同,随线程而生,随线程而灭。因为是线程私有的,所以不需要考虑线程安全问题。
2.1 程序计数器 (Program Counter Register)
- 作用:当前线程所执行的字节码的行号指示器。
- 特点 :这是 JVM 规范中唯一一个没有规定
OutOfMemoryError的区域。 - 比喻:就像看书时的"书签",记录读到了哪里,下次回来接着读。
2.2 虚拟机栈 (VM Stack)
这是 Java 方法执行的内存模型。每个方法在执行时都会创建一个栈帧 (Stack Frame)。
栈帧内部结构图
栈帧 (Stack Frame) 局部变量表 (Local Variable Table)
存储: boolean, byte, char, short, int, float, reference... 操作数栈 (Operand Stack)
中转站: 算术运算的地方 动态链接 (Dynamic Linking)
指向常量池的方法引用 方法返回地址 (Return Address)
恢复上层方法状态
- 异常 :
StackOverflowError: 线程请求的栈深度大于虚拟机允许的深度(通常是递归过深)。OutOfMemoryError: 如果栈可以动态扩展,扩展时无法申请到足够的内存。
3. 线程共享区域:海量数据存储
这部分区域由所有线程共享,是 JVM 内存管理的重头戏,也是垃圾收集器(GC)主要工作的区域。
3.1 堆 (Heap)
- 作用:存放对象实例和数组。
- 结构 :为了更好地回收内存,堆通常被划分为新生代 (Young Gen) 和 老年代 (Old Gen)。
对象在堆中的流转
老年代 Survivor区(S0/S1) Eden区 分配器 老年代 Survivor区(S0/S1) Eden区 分配器 对象优先在 Eden 分配 Minor GC (Young GC) 发生 经过多次 GC (默认15岁) Major GC / Full GC 清理老年代 new Object() 存活对象复制到 S0 晋升到老年代
3.2 方法区 (Method Area) / 元空间 (Metaspace)
- 作用 :存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等。
- 演变 :
- JDK 7 及之前:称为"永久代" (PermGen),位于堆中。
- JDK 8 及之后:称为"元空间" (Metaspace),使用本地内存 (Native Memory),不再受 JVM 堆大小限制,只受限于物理内存。
4. 综合视角:一个对象是如何创建的?
当我们在代码中写下 Object obj = new Object(); 时,JVM 内部发生了什么?这涉及到了栈、堆和方法区的协同工作。
对象创建流程图
No
Yes
No
No
Yes
开始
类是否已加载?
类加载器加载类信息
存入方法区/元空间
堆内存是否足够?
触发 GC
GC后内存足够?
抛出OutOfMemoryError
在堆中划分内存块
初始化零值
(int=0, boolean=false)
设置对象头
(哈希码, GC分代年龄, 锁状态)
执行 方法
将堆地址赋值给栈中的引用变量
结束
内存指向示意
java
public void method() {
// "user" 引用存在于 [虚拟机栈] 的局部变量表中
// "new User()" 实例存在于 [堆] 中
User user = new User();
}
- 栈 (Stack) : 存储引用
user(本质是一个内存地址,如0x1234). - 堆 (Heap) : 存储
User对象的实例数据 (地址0x1234). - 方法区 (Method Area) : 存储
User类的类型信息 (字段定义、方法字节码等). - 对象头: 堆中的对象头里有一个指针,指向方法区中的类元数据。
理解 JVM 内存模型是编写高性能、高稳定性 Java 代码的关键:
- 栈 (Stack) 管运行:处理方法调用,空间小,速度快,线程私有。
- 堆 (Heap) 管存储:存放对象实例,空间大,垃圾回收的主战场,线程共享。
- 方法区 (Method Area) 管类信息:存放 Class 结构,JDK 8 后变为元空间。
- 程序计数器 (PC) 管流程:记录执行位置,唯一不会 OOM 的区域。
掌握这些区域的职责与交互,能让你在面对复杂的并发问题和内存溢出故障时,做到心中有数,游刃有余。