跳转至

Java GC 机制理解

一、什么样的对象可以被回收

1. 引用计数法(已淘汰)

每个对象加一个计数器,被引用 +1,引用失效 -1。无法解决循环引用(A 引用 B、B 引用 A),Java 不用。

2. 可达性分析(Reachability Analysis)

GC Roots 出发,沿引用链搜索,不可达的对象就是可回收的。

GC Roots 包括:

  • 虚拟机栈(栈帧中的局部变量表)引用的对象。
  • 方法区中静态变量引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈 JNI 引用的对象。
  • 同步锁 synchronized 持有的对象。
  • JVM 内部引用(基本类型 Class、异常对象、类加载器)。

二、引用类型

类型 回收时机
强引用 StrongReference 只要还活着就不回收
软引用 SoftReference 内存不足时回收(缓存常用)
弱引用 WeakReference 下次 GC 必回收(ThreadLocalMap 的 key)
虚引用 PhantomReference 跟踪对象被回收的通知

三、分代收集理论

JVM 把堆分成新生代老年代

  • 新生代(Young Gen):对象朝生夕死,用复制算法
  • Eden : Survivor0 : Survivor1 = 8 : 1 : 1。
  • Minor GC:在 Eden 满时触发,存活对象复制到 Survivor,年龄 +1。
  • 老年代(Old Gen):存活时间长的对象,用标记-清除标记-整理
  • Major GC / Full GC:老年代满时触发。

对象进入老年代的条件:

  1. 年龄达到阈值(默认 15,-XX:MaxTenuringThreshold)。
  2. 大对象直接进老年代(-XX:PretenureSizeThreshold)。
  3. 动态年龄判断:Survivor 中相同年龄对象大小超过 Survivor 一半,年龄 ≥ 该年龄的直接进老年代。
  4. 空间分配担保:Minor GC 前检查老年代最大连续空间是否够新生代全部对象,不够则看是否允许担保失败。

四、垃圾收集算法

1. 标记-清除(Mark-Sweep)

标记可回收对象,然后清除。缺点:碎片多。

2. 复制算法(Copying)

把内存分成两块,每次用一块,GC 时把存活对象复制到另一块。无碎片,但浪费空间。新生代用。

3. 标记-整理(Mark-Compact)

标记后把存活对象向一端移动,然后清理边界外。老年代用。

4. 分代收集

新生代用复制,老年代用标记-整理。

五、常见垃圾收集器

收集器 分代 特点
Serial / Serial Old 新生代 / 老年代 单线程,Client 模式
ParNew 新生代 Serial 多线程版,配合 CMS
Parallel Scavenge 新生代 吞吐量优先
Parallel Old 老年代 吞吐量优先
CMS 老年代 低停顿,标记-清除,有碎片,JDK 9 废弃
G1 整堆 分区化,可预测停顿,JDK 9 默认
ZGC 整堆 亚毫秒级停顿,TB 级堆
Shenandoah 整堆 与 G1 类似,并发整理

六、GC 触发条件

  • Eden 区满 → Minor GC。
  • 老年代满 → Full GC。
  • 方法区满 / 常量池回收不了 → Full GC。
  • System.gc() 建议(不保证)。
  • 空间分配担保失败。

面试加分

  • 什么是 STW(Stop The World)?GC 时所有用户线程暂停。
  • G1 的"Remembered Set"和"Card Table"用来跨代引用查找,避免全堆扫描。
  • CMS 为什么会有碎片?标记-清除算法不整理。