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:老年代满时触发。
对象进入老年代的条件:
- 年龄达到阈值(默认 15,
-XX:MaxTenuringThreshold)。 - 大对象直接进老年代(
-XX:PretenureSizeThreshold)。 - 动态年龄判断:Survivor 中相同年龄对象大小超过 Survivor 一半,年龄 ≥ 该年龄的直接进老年代。
- 空间分配担保: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 为什么会有碎片?标记-清除算法不整理。