一、内存区域:办公室功能区划分
比喻:JVM 内存就像一个公司的办公区,不同区域有不同用途
1. 堆(Heap)—— 大仓库
-
作用:存放所有对象实例(所有员工领的办公用品都堆在这)
-
特点:
- 线程共享(所有人随便拿)
- 会抛出
OutOfMemoryError(仓库爆满)
-
源码验证:HotSpot 的
CollectedHeap类(如genCollectedHeap.cpp)// 堆内存分配核心逻辑(伪代码) HeapWord* GenCollectedHeap::mem_allocate(...) { if (尝试快速分配失败) { 触发GC(); // 保洁阿姨清理仓库 再次尝试分配; } if (仍失败) throw OutOfMemoryError; }
2. 虚拟机栈(Stack)—— 员工工位
-
作用:存放方法调用时的栈帧(每个员工处理任务时的临时笔记本)
-
特点:
- 线程私有(每人有自己的工位)
- 会抛出
StackOverflowError(工位被笔记本堆满)
-
栈帧结构:
| 局部变量表 | 操作数栈 | 动态链接 | 方法返回地址 | -
源码对应:
frame.hpp中的frame类(描述栈帧结构)
3. 方法区(Method Area)—— 档案室
-
作用:存储类信息、常量、静态变量(公司规章制度、员工档案)
-
JDK 变化:
- JDK7:永久代(档案室在办公楼内)
- JDK8+:元空间(Metaspace,档案室搬到云盘,用本地内存)
-
源码线索:
metaspace.hpp中的Metaspace类// 元空间内存分配(伪代码) void* Metaspace::allocate(size_t size) { if (当前块不够) { 向操作系统申请新内存块; // 类似 malloc } return 分配地址; }
4. 程序计数器(PC Register)—— 任务进度条
- 作用:记录当前线程执行到的字节码行号(员工知道自己做到哪一步了)
- 唯一不会 OOM 的区域(老板必须知道每个员工的进度)
5. 本地方法栈(Native Method Stack)—— 外籍员工工位
- 作用:执行 Native 方法(比如 C/C++ 写的功能)
6. 直接内存(Direct Memory)—— 外包仓库
- 特点:通过
ByteBuffer.allocateDirect()申请,不受 JVM 堆限制 - 底层原理:调用
unsafe.allocateMemory()直接分配系统内存
二、内存模型(JMM):办公室协作规则
比喻:JMM 是规范多线程如何安全传递数据的规则手册
1. 主内存 vs 工作内存
-
主内存:公司公告板(所有线程可见)
-
工作内存:员工私人笔记本(线程私有,存主内存的数据副本)
-
数据同步问题:
复制
线程A修改笔记本 → 未同步到公告板 → 线程B看到旧数据(脏读)
2. 原子性/可见性/有序性
-
原子性:
synchronized锁会议室(一次只允许一个线程操作)- 源码实现:
objectMonitor.cpp中的锁竞争逻辑
-
可见性:
-
volatile强制要求员工修改笔记本后立刻抄到公告板 -
底层原理:通过 CPU 的
MESI 协议或内存屏障实现// HotSpot 的 volatile 写操作插入内存屏障 OrderAccess::storeload(); // 写后加屏障,强制刷新
-
-
有序性:
- 禁止指令重排序(员工必须按流程步骤办事)
happens-before 原则(公司规定的优先顺序)
3. 内存屏障(Memory Barrier)—— 行政监督
- LoadLoad屏障:确保读操作顺序
- StoreStore屏障:确保写操作顺序
- LoadStore屏障:读不能重排到写之后
- StoreLoad屏障:全能屏障(最严格)
4. synchronized 的锁升级
-
无锁 → 偏向锁:第一个员工独占会议室(Mark Word 记录线程ID)
-
偏向锁 → 轻量级锁:有竞争时升级为轮流使用(自旋等待)
-
轻量级锁 → 重量级锁:竞争激烈时找行政协调(操作系统互斥量)
-
源码证据:
markOop.hpp中锁状态标记位enum { locked_value = 0, // 轻量锁 unlocked_value = 1, // 无锁 monitor_value = 2, // 重量锁 marked_value = 3, // GC标记 biased_lock_pattern = 5 // 偏向锁 };
三、从源码看内存管理(HotSpot 核心逻辑)
1. 对象内存分配
-
快速分配:TLAB(Thread Local Allocation Buffer)
- 每个线程在堆里有一小块自留地
- 源码:
thread.cpp中ThreadLocalAllocBuffer类
2. GC 触发条件
-
Young GC:Eden 区满时触发
// GenCollectorPolicy 判断是否触发GC if (eden_space->used() > eden_space->capacity_in_bytes()) { collect_generation(...); // 开始打扫年轻代 } -
Full GC:老年代或元空间不足时触发
3. 内存溢出(OOM)真相
- 堆溢出:创建大对象或内存泄漏(如循环引用未断开)
- 栈溢出:递归调用无终止条件
- 元空间溢出:加载过多类(比如动态生成类)
四、高频面试题解析
-
String 存在哪里?
String s = "abc"→ 常量池(方法区)String s = new String("abc")→ 堆 + 常量池
-
volatile 和 synchronized 的区别?
volatile是轻量级可见性控制synchronized是重量级原子性+可见性控制
-
为什么要有元空间替代永久代?
- 避免永久代大小难预估导致 OOM
- 元空间使用本地内存,上限由系统决定
五、总结口诀
「内存区域分六块,堆栈方法各不同
JMM管线程事,可见有序原子性
偏向轻量重量锁,屏障插入保顺序
元空间换永久代,直接内存更高效!」
附:常见内存相关参数
-Xmx:堆最大值-Xss:栈大小-XX:MetaspaceSize:元空间初始大小-XX:MaxDirectMemorySize:直接内存上限