堆内存是GC唯一真正干活的地方,程序计数器、虚拟机栈、本地方法栈生命周期与线程绑定,无需GC;方法区(Metaspace)回收条件苛刻、收益低;99%回收任务由Heap承担,对象分配于新生代Eden区,经Minor GC存活后进入Survivor,达年龄阈值或超大对象直接晋升老年代;Full GC代价高,应避免过早晋升;Metaspace虽取代PermGen,但配置不当仍会OOM;判断对象可回收仅依赖可达性分析,非引用计数;G1回收器按Region分区回收,Mixed 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(默认
Old Generation)主要靠 Full GC 回收,但代价高、停顿长,应尽量减少对象过早晋升。JDK 8 彻底移除了永久代(PermGen),类元数据(类名、字段、方法签名、常量池等)改存本地内存,由 Metaspace 管理。这不是“不用管了”,而是换了一种溢出方式:java.lang.OutOfMemoryError: Metaspace。
-XX:MetaspaceSize 是初始触发 GC 的阈值(非最大值),设太小会导致频繁 GC;-XX:MaxMetaspaceSize 必须显式设置(默认无上限),否则可能耗尽本地内存,拖垮整个系统;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(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 等参数影响混合回收节奏,盲目调参反而加剧波动;G1 更稳更快,别迷信“新就是好”。真正卡住人的从来不是概念,而是 MetaspaceSize 忘设、PretenureSizeThreshold 乱调、把 SoftReference 当强引用用、或者以为开了 G1 就不用看 GC 日志——这些细节不落地,模型再熟也没用。