Java垃圾回收机制的基本概念与流程

Java GC是分代、可调优的运行时机制,通过可达性分析判定垃圾,新生代采用Eden/S0/S1复制回收,G1与ZGC设计目标不同:G1基于Region预测回收,ZGC依赖着色指针实现超低延迟。

Java 垃圾回收(GC)不是“自动清内存”这么简单——它是一套有策略、分代、可调优的运行时机制,不理解触发条件和回收范围,很容易在 OutOfMemoryErrorStop-The-World 暂停时措手不及。

哪些对象会被判定为“垃圾”?

Java 不用引用计数,而是用**可达性分析**:从 GC Roots 出发,不可达的对象才可能被回收。GC Roots 包括:

  • 栈帧中的局部变量表 引用的对象
  • 方法区中静态属性 引用的对象
  • 方法区中常量 引用的对象(如字符串常量池里的 "abc"
  • 本地方法栈中 JNI 引用的对象

注意:finalize() 方法已被标记为 @Deprecated,且仅最多执行一次,不能当作资源清理主逻辑;依赖它延迟回收,大概率导致内

存堆积。

为什么新生代要分 Eden、S0、S1?

绝大多数对象朝生暮死,分代 + 空间划分是为了让 Minor GC 更快——复制算法比标记清除/整理更适合小空间高频回收。

典型分配与回收流程:

  • 新对象优先分配到 Eden
  • Minor GC 触发时,存活对象被复制到 Survivor(S0 或 S1),年龄 +1
  • 对象年龄达到 MaxTenuringThreshold(默认 15)或 Survivor 空间不足,直接晋升到 Old
  • S0S1 总是保持一个为空,用于复制交换

如果 Eden 区填满但 Minor GC 后仍无法腾出足够空间,会触发 Allocation Failure 并尝试分配担保(如直接进老年代),失败则抛 OutOfMemoryError: Java heap space

常见的 GC 日志怎么看?

-XX:+PrintGCDetails -Xloggc:gc.log 后,一行典型日志:

[GC (Allocation Failure) [PSYoungGen: 123456K->12345K(131072K)] 123456K->123456K(262144K), 0.0123456 secs]

关键字段含义:

  • PSYoungGen:使用 Parallel Scavenge 收集器的新生代
  • 123456K->12345K:GC 前后新生代占用,差值≈回收量
  • (131072K):新生代总容量
  • 123456K->123456K:整个堆 GC 前后占用(说明老年代没动)
  • 0.0123456 secs:Stop-The-World 时间

若发现 Full GC 频繁或老年代占用持续上涨,大概率存在内存泄漏,不是调大堆就能解决的。

G1 和 ZGC 的核心差异在哪?

它们都面向低延迟,但设计哲学不同:

  • G1 把堆划成固定大小 Region,通过预测模型选择收益最高的 Region 回收(Garbage-First),仍需 STW,但可控;适合 4–64GB 堆,要求暂停时间
  • ZGC 使用着色指针 + 读屏障,大部分阶段并发执行,STW 控制在 ,支持 TB 级堆;但要求 JDK 11+,且对 Linux /proc/sys/vm/max_map_count 有最低要求(通常需 ≥ 1048576)

别只看“ZGC 更先进”,实际压测中若应用大量使用反射或 JNI,ZGC 可能因读屏障开销反而不如 G1 稳定。

GC 行为高度依赖具体收集器、JDK 版本、堆结构和业务对象生命周期模式。线上调优前,先用 jstatJFR 看真实行为,而不是照搬“-XX:+UseG1GC”就以为万事大吉。