这是「一文拿下 GC」系列第五篇(终章)。前面讲了「判断该死 + 怎么回收 + 哪个收集器」——这一篇回到最底层:内存区域本身长什么样、Eden/Survivor/Old 怎么协作、为什么 Java 8 要废弃 PermGen 改用 Metaspace。
一、JVM 内存结构总览

两大阵营:
- 堆区(Heap):Young + Old
- 非堆区(Non-Heap):Metaspace + CCS + CodeCache
2017 年冷知识:当年 Java 8(2014 年发布)已经把 PermGen 换成 Metaspace——这是 HotSpot 内存模型最大的一次改动。Metaspace 直接使用本地内存,不再受限于 JVM 堆。
二、堆区(Heap)详解
2.1 Eden 区
Eden 用来分配新创建的对象。
TLAB(Thread Local Allocation Buffer):
- 通常会有多个线程同时创建多个对象,Eden 被划分成多个线程本地分配缓冲区
- 大部分线程直接在线程的 TLAB 中分配,避免与其他线程同步操作
- 如果 TLAB 没有足够的空间,就会在共享的 Eden(shared Eden space)中分配
- 如果共享 Eden 区也没有足够的空间,会触发一个年轻代 GC 来释放内存空间
- 如果 GC 后依然没有空间,则直接分配到老年代
2.2 Survivor 区
- survivor 区分两块,称为 s0 和 s1,也叫 from 空间和 to 空间
- 任意时刻总有一块区域是空的——空的那个存活区用于在下一次年轻代 GC 时存放收集的对象
- 年轻代中所有的存活对象(包括 Eden 区和非空的那个 “from” 存活区)都会被复制到 “to” 存活区
- GC 过程完成后,“to” 区有对象,而 “from” 区里没有对象——两者的角色正好交换
2.3 Tenuring Threshold(晋升阈值)
为了确定一个对象是否「足够老」,可以被提升(Promotion)到老年代:
- GC 模块跟踪记录每个存活区对象存活的次数
- 每次分代 GC 完成后,存活对象的年龄就会增长
- 当年龄超过提升阈值(tenuring threshold),就会被提升到老年代区域

晋升阈值的确定:
- 具体的提升阈值由 JVM 动态调整(自适应)
- 也可用参数
-XX:+MaxTenuringThreshold来指定上限 - 现代 JVM 中这个阈值默认设置为 15 个 GC 周期(这也是 HotSpot 中的最大值)
- 如果存活区空间不够存放年轻代中的存活对象,提升(Promotion)也可能更早地进行
- 如果设置
-XX:+MaxTenuringThreshold=0,则 GC 时存活对象不在存活区之间复制,直接提升到老年代
2.4 Old Generation(老年代)
经过多次 GC 仍然存活的对象晋升到这里。
- Old 区对象存活率高、回收频率低
- 一旦发生 Old GC / Full GC,停顿时间通常比 Young GC 长得多
- 收集器在 Old 区的实现:CMS(标记-清除)、Parallel Old(标记-整理)、G1(区域化)
三、非堆区(Non-Heap)详解
3.1 PermGen(永久代)—— Java 8 之前
PermGen 是什么:
- 在 Java 8 之前有一个特殊的空间,称为「永久代」(Permanent Generation)
- 这是存储元数据(metadata)的地方,比如 class 信息等
- 此外,这个区域中也保存有其他的数据和信息,包括内部化的字符串(internalized strings)等
PermGen 的痛点:
实际上这给 Java 开发者造成了很多麻烦——因为很难去计算这块区域到底需要占用多少内存空间。预测失败导致的结果就是产生
java.lang.OutOfMemoryError: PermGen space这种形式的错误。
除非 OutOfMemoryError 确实是内存泄漏导致的,否则就只能增加 permgen 的大小:
-XX:MaxPermSize=256
3.2 Metaspace(方法区的实现)—— Java 8 之后
为什么废弃永久代(PermGen)迎来元空间(Metaspace):
《JVM 虚拟机规范》只规定了方法区的这么个概念和作用,并没有规定怎么去实现它。在其他的 JVM 中不存在永久代,而 HotSpot 中存在永久代(jdk1.7 及以前)。
废弃 PermGen 的原因:
- 移除永久代是为了融合 HotSpot 和 JRockit 而做努力——JRockit 早就没有永久代
- 不再需要配置永久代——配置永久代,经常会在使用时不够用或者发生内存泄露
- 在使用 PermGen 时,通过
-XX:PermSize和-XX:MaxPermSize控制大小,JVM 在启动时会根据 PermSize 初始化一块连续的内存 - 如果设置的过大非常浪费,而设置的 MaxPermSize 也是限制好的
- 实际项目中,很难确定到底这个值设置多少合适而导致内存溢出
Metaspace 的特点:
- 元空间与永久代类似,都是对 JVM 规范中方法区的实现
- 不过元空间不在虚拟机中,而是直接使用本地内存
- 使用
-XX:MetaspaceSize=xxx可以控制元空间发生 GC 的阈值(并不会限制 Metaspace 初始化大小) - 默认情况下这个值根据不同平台在 12M 到 20M 浮动
- 使用
-XX:MaxMetaspaceSize=xxx可以限制 Metaspace 的增长上限,防止某些情况下某程序应 Metaspace 扩容而无限使用本地内存,影响其他程序
3.3 其他变化
字符串常量池
字符串常量由方法区中移动到堆中——这是 Java 7 开始的改动。
String.intern()的行为也变了:在 Java 7+ 真正复用堆中已存在的字符串引用,而不是在 PermGen 中存一个 copy。
CCS(Compressed Class Space,压缩类空间)
内存中指向对象实例的指针,在 LP64 系统中,需要用 64 位来表示,ILP32 的系统中则只需要 32 位——这就是在 LP64 系统中所有引用程序运行时都会比 ILP32 大 1.5 倍左右,因为指针占用空间增加了。
CompressedOops(压缩普通对象指针):
- 压缩后的一般对象指针在使用时需要将 32 位整型按因数 8 进行扩展
- 并加到一个 64 位的基础地址上,从而找到所指向的对象
- 这种方法可以表示四十亿个对象,相当于 32GB 的堆内存
- 同时,使用此法压缩数据结构也能达到和 ILP32 系统相近的效果
关闭参数:
-XX:-UseCompressedOops # 关闭指针压缩
-XX:-UseCompressedClassPointers # 关闭压缩类空间段指针
CodeCache
JIT 编译的本地代码存放空间——JIT 编译器把热点字节码编译成本地机器码后存在这里。
- 设置
-Xint使用完全解释执行 → 这片区域就不会有数据
四、4 种引用类型(JDK 1.2 引入)
| 引用类型 | 描述 | 回收时机 |
|---|---|---|
| 强引用(Strong Reference) | 代码中普通的引用,类似于 Object obj = new Object(); | 引用还存在,垃圾回收器就不会回收 |
| 软引用(Soft Reference) | 描述非必须的对象 | 系统要发出内存溢出异常前会将这些对象列进回收范围之中进行第二次回收——如果这次回收后还没有足够的内存将会抛出 OOM |
| 弱引用(Weak Reference) | 描述非必须的对象,强度比软引用更弱 | 被弱引用关联的对象只会生存到下一次 GC 之前——当 GC 工作,无论当前内存是否足够都会回收被弱引用关联的对象 |
| 虚引用(Phantom Reference) | 也被称为幽灵引用或幻影引用 | 一个对象是否有虚引用,完全不对其生存时间造成影响,也无法通过虚引用来取得对象实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被收集器回收时收到一个系统通知 |
2017 年冷知识:当年软引用和弱引用的典型应用是本地缓存(如 Guava Cache 早期版本、WeakHashMap)和内存敏感的对象池。今天(2026 年)Ehcache 3.x、Caffeine 等本地缓存库已经很少直接用 SoftReference/WeakReference——而是自己管理淘汰策略,JVM GC 配合做兜底回收。
五、GC 系列小结
到这里「一文拿下 GC」系列 5 篇全部完成:
- GC 调优入门(gc-tuning-and-tools)—— 参数 + 工具 + 参考值
- 怎么判断对象该死(gc-object-liveness)—— 引用计数 vs 可达性
- 垃圾收集算法三板斧(gc-algorithms)—— 标记-清除 / 复制 / 标记-整理
- 垃圾收集器大阅兵(gc-collectors)—— Serial/ParNew/Parallel/CMS/G1
- 堆内存区域全景(gc-memory-regions)—— Eden/Survivor/Old + PermGen/Metaspace(本文)
加上之前的「一文拿下 JVM」系列 5 篇(内存区域 + 参数命令 + 逃逸分析 + OOM 工具 + 对象分配),JVM + GC 共 10 篇完整覆盖 Java 内存管理——从「JVM 怎么跑」到「JVM 怎么回收」的全链路知识。
2017 年冷知识:当年很多人把 JVM 和 GC 当作「高级话题」敬而远之——其实真正能搞懂 GC 调优的后端工程师,在团队里是凤毛麟角。今天(2026 年)虽然有 ZGC、Shenandoah、Generational ZGC 等新一代低延迟收集器,但底层原理(分代 + 标记-整理 + 复制)没变——这一系列的内容在 2026 年依然适用,只是参考值要更新。