堆内存是GC唯一真正干活的地方,程序计数器、虚拟机栈、本地方法栈生命周期与线程绑定,无需GC;方法区(Metaspace)回收条件苛刻、收益低;99%回收任务由Heap承担,对象分配于新生代Eden区,经Minor GC存活后进入Survivor,达年龄阈值或超大对象直接晋升老年代;Full GC代价高,应避免过早晋升;Metaspace虽取代PermGen,但配置不当仍会OOM;判断对象可回收仅依赖可达性分析,非引用计数;G1回收器按Region分区回收,Mixed GC兼顾新老代,但非全自动,需合理调参。

堆内存是GC唯一真正干活的地方
程序计数器、虚拟机栈、本地方法栈这三块区域,生命周期和线程完全绑定,入栈出栈时机确定,根本不需要GC介入。方法区(JDK 8+ 是 Metaspace)虽然理论上可回收废弃类和常量,但实际触发条件苛刻、收益极低,生产环境几乎不靠它减压。真正承担99%内存回收任务的,只有 Heap —— 所有 new 出来的对象、数组都落在这儿,也是 OutOfMemoryError: Java heap space 的唯一来源。
- 新生代(
Young Generation)默认占堆 1/3,含Eden(约80%)、Survivor S0和S1(各约10%),对象优先分配在Eden; - 一次
Minor GC后存活对象进入Survivor,每经历一次 GC 年龄+1,达-XX:MaxTenuringThreshold(默认15)后晋升老年代; - 大对象(如超长字符串、大数组)若超过
-XX:PretenureSizeThreshold(默认0,即禁用),会直接进老年代,避免在新生代反复拷贝; - 老年代(
Old Generation)主要靠Full GC回收,但代价高、停顿长,应尽量减少对象过早晋升。
Metaspace 已取代 PermGen,但配置不当照样 OOM
JDK 8 彻底移除了永久代(PermGen),类元数据(类名、字段、方法签名、常量池等)改存本地内存,由 Metaspace 管理。这不是“不用管了”,而是换了一种溢出方式:java.lang.OutOfMemoryError: Metaspace。
-
-XX:MetaspaceSize是初始触发 GC 的阈值(非最大值),设太小会导致频繁 GC; -
-XX:MaxMetaspaceSize必须显式设置(默认无上限),否则可能耗尽本地内存,拖垮整个系统; - Spring Boot + CGLIB 动态代理、热部署(如 DevTools)、OSGi 插件场景极易生成大量类,是
Metaspace溢出高发区; -
字符串常量池(
String Table)已从方法区移到堆中,所以intern()过多会撑爆堆,而非 Metaspace。
判断对象是否可回收,只看可达性分析,不是引用计数
JVM 不用引用计数法(Reference Counting),因为解决不了循环引用问题。所有主流实现都基于可达性分析(Reachability Analysis):以一组称为 GC Roots 的对象为起点,向下搜索引用链;任何对象到 GC Roots 不可达,即判定为可回收。
-
GC Roots包括:正在执行的线程的栈帧中的局部变量、本地方法栈中 JNI 引用、方法区中类静态属性引用、方法区中常量引用; - 即使对象重写了
finalize(),也仅获得一次“复活”机会(且 JDK 9+ 已标记为废弃),不应依赖; -
SoftReference/WeakReference/PhantomReference的行为差异极大,比如缓存用SoftReference,监听对象销毁用PhantomReference,别混用; - 使用
System.gc()只是建议 JVM 执行 GC,不保证立即发生,生产环境禁止调用。
G1 回收器不是万能药,混合回收逻辑容易误判
G1(Garbage-First)是 JDK 9 默认、JDK 10+ 唯一支持的默认 GC,主打可预测停顿时间(-XX:MaxGCPauseMillis),但它不是“全自动优化器”。它的核心是把堆划分为多个大小相等的区域(Region),并按垃圾密度排序回收。
-
G1的Mixed GC阶段既清理部分Young区,也选几个垃圾最多的Old区一起回收——这意味着老年代碎片化仍存在,且不会像 CMS 那样彻底并发清理; - 若
Humongous Region(超大对象占用的连续 Region)过多,会引发频繁Full GC,此时需调大-XX:G1HeapRegionSize或控制对象大小; -
-XX:G1MixedGCCountTarget和-XX:G1OldCSetRegionThresholdPercent等参数影响混合回收节奏,盲目调参反而加剧波动; - 小堆(Parallel GC 往往比
G1更稳更快,别迷信“新就是好”。
真正卡住人的从来不是概念,而是 MetaspaceSize 忘设、PretenureSizeThreshold 乱调、把 SoftReference 当强引用用、或者以为开了 G1 就不用看 GC 日志——这些细节不落地,模型再熟也没用。










