Skip to content

概念卡片:JVM 垃圾回收 GC

一句话机制

GC 分两步:先「判定对象已死」(可达性分析,从 GC Roots 出发找引用链),再「回收」——按分代选算法(新生代标记-复制、老年代标记-整理),用不同收集器(Serial/ParNew/Parallel/CMS/G1)在吞吐量与停顿时间之间取舍。 这是 JVM 调优的核心前置。

判定对象已死

方法原理问题
引用计数法每处引用 +1,失效 -1,归 0 即死循环引用无法回收(Obj1↔Obj2 互相引用)
可达性分析从 GC Roots 沿引用链遍历,不可达即死JVM 采用(解决循环引用)

GC Roots 四类:栈帧局部变量表引用的对象 · 本地方法栈 JNI 引用 · 静态属性引用 · 常量引用。

可达性分析要求「一致性快照」,最简单是 Stop The World;若与用户线程并发,用增量更新(CMS)或原始快照 SATB(G1)记录变化。

四种引用类型

引用回收时机
强引用永不回收(不置 null)
软引用 SoftReference内存不足才回收(可做缓存)
弱引用 WeakReference一触发 GC 就回收(ThreadLocal 用)
虚引用 PhantomReference回收时收系统通知(直接内存管理)

四大回收算法

算法适用优点缺点
标记-清除老年代(CMS)不移动对象、停顿短内存碎片
标记-复制新生代无碎片、快空间利用率低、对象存活率高时效率低
标记-整理老年代无碎片移动对象、必须 STW
分代整体策略各代用最优算法

新生代复制算法的改良:不 1:1 划分,而是 Eden:S0:S1 = 8:1:1,只复制存活对象(因为绝大多数对象熬不过第一轮 GC),装不下的直接进老年代。

收集器全景

新生代(复制算法)            老年代
Serial ──────────────── Serial Old(标记-整理)
ParNew ──┐
Parallel Scavenge ── Parallel Old
         └── CMS(标记-清除,可配 ParNew)
G1(整个堆,Region 布局)
收集器线程目标特点
Serial单线程Client 模式,STW
ParNew多线程唯一能与 CMS 搭配的新生代收集器
Parallel Scavenge多线程吞吐量优先-XX:MaxGCPauseMillis/GCRatio + 自适应
CMS并发最短 STW四步 + 并发失败/碎片问题
G1并发可预测停顿Region + 记忆集,JDK9 起默认

CMS 四步与三大缺点

初始标记(STW) → 并发标记(增量更新) → 重新标记(STW) → 并发清除
缺点说明解法
CPU 敏感并发阶段占 CPU,应用变慢
并发失败 CMF并发期间用户线程耗尽预留内存调高开始 GC 的阈值
晋升失败 + 碎片标记-清除产生碎片,放不下晋升对象CMSFullGCsBeforeCompaction 定时整理

G1 核心(JDK9 起默认)

  • Region 布局:堆分成大小相同的 Region(默认 2048 个),每个 Region 可扮演 Eden/Survivor/Old/Humongous(大对象)四种角色
  • 可预测停顿:用户指定期望停顿(单次 STW 默认 ≤200ms),G1 维护「回收收益 vs 成本」优先级列表,只回收收益最大的 Region
  • 记忆集 + 卡表:解决跨 Region 引用,避免全堆扫描(每个 Region 记录谁指向自己)
  • SATB 原始快照:并发标记用原始快照保证一致性
  • 四步:初始标记 → 并发标记 → 最终标记(STW)→ 筛选回收(STW,并行复制)
  • Mixed GC:G1 特有,回收新生代 + 部分老年代

GC 划分与触发

GC 类型回收范围触发
Young GC新生代Eden 满
Old GC老年代仅 CMS 有
Mixed GC新生代 + 部分老年代仅 G1 有
Full GC整堆 + 方法区老年代不足/空间分配担保失败/System.gc()/方法区不足

常见误解(避坑)

  1. ❌ "引用计数是 JVM 的判定方式"。→ JVM 用可达性分析,引用计数有循环引用问题。
  2. ❌ "G1 也是分新生代老年代两块的固定布局"。→ G1 是 Region 化的,Region 角色动态变化,不是固定两块。
  3. ❌ "CMS 没有 STW"。→ CMS 的初始标记和重新标记都要 STW,只是把耗时最长的并发标记/清除放到了并发阶段。
  4. ❌ "对象没被引用就一定立即回收"。→ 还要经过 finalize()(可自救一次),且软引用对象在内存充足时不会被回收。

关联

最近更新