JVM内存结构按线程视角分为私有(程序计数器、虚拟机栈、本地方法栈)和共享(堆、元空间)区域;对象生命周期遵循Eden→Survivor→老年代分代晋升逻辑,大对象可直接入老年代,元空间取代永久代使用本地内存。

直接抓住“线程视角”和“对象生命周期”两条主线,就能把 JVM 内存结构理清楚,不用死记硬背。
先分清线程私有 vs 线程共享
这是理解各区域作用和异常类型的基础:
- 线程私有:程序计数器、虚拟机栈、本地方法栈——每个线程一份,互不干扰;其中程序计数器最小、最安全,是唯一不会 OOM 的区域;栈深度超限抛 StackOverflowError,栈空间不足才抛 OOM。
- 线程共享:堆、方法区(JDK8+ 是元空间)——所有线程共用,也是 OOM 高发区;堆存对象实例,元空间存类元数据(如类名、字段、常量池、静态变量),两者都可能因持续增长而触发 OOM。
重点吃透堆的分代逻辑与演进
堆是 GC 主战场,复习时盯住“对象去哪儿了”和“为什么这么设计”:
- 新对象默认分配在 Eden 区;一次 Minor GC 后存活的进入 Survivor(S0/S1),每经历一次 GC,年龄 +1;达到阈值(默认 15)或 Survivor 放不下,就晋升到 老年代。
- JDK8 默认使用 Parallel 收集器,严格依赖分代模型(Eden+S0+S1+老年代,默认 8:1:1);JDK21 默认 ZGC 已取消分代,采用区域化管理,堆结构更灵活,复习时要区分 JDK 版本对应的默认行为。
- 常见误区:Survivor 不是“必须经过两次 GC 才能进老年代”,而是看对象年龄和空间压力;大对象(如大数组)可能直接进老年代(-XX:PretenureSizeThreshold 控制)。
方法区记住“永久代 → 元空间”的替换本质
这不是简单改个名字,而是内存管理和 OOM 原因的根本变化:
- JDK7 及以前:永久代在堆内,受 -XX:MaxPermSize 限制,容易因加载大量类(如热部署、反射生成类)导致 OOM。
- JDK8+:元空间使用本地内存(Native Memory),默认无上限(受限于系统内存),OOM 更多由元空间泄漏(如未卸载的 ClassLoader)引发,可通过 -XX:MaxMetaspaceSize 显式限制。
- 常量池也拆开了:字符串常量池移到堆中(所以 intern() 行为在 JDK7+ 有变化),而类字面量、符号引用等保留在元空间。
结合异常反推区域特征
面试和排查时,异常是快速定位问题的钥匙:
- java.lang.OutOfMemoryError: Java heap space → 堆不够,先看对象是否堆积(内存泄漏)、还是堆参数太小(-Xms/-Xmx 不合理)。
- java.lang.OutOfMemoryError: Metaspace → 元空间爆了,查动态生成类(CGLIB、JSON 反序列化)、类加载器未释放。
- java.lang.StackOverflowError → 递归太深或局部变量太多,基本锁定虚拟机栈。
- 没有 “OutOfMemoryError: Program Counter” —— 因为它只存一个指针,极小且不扩容。











