JVM知识梳理(2)

JVM知识梳理(2)

作者:没有四次元口袋的蓝胖

日期:2026-07-31

标签:Java, JVM, GC算法, CMS, G1


JVM 垃圾回收是面试重点。核心掌握:如何判断对象可回收、四种 GC 算法、CMS 和 G1 回收器的区别。


一、如何判断对象可以回收?

1.1 引用计数法

每个对象维护一个引用计数器,被引用时 +1,引用失效时 -1,为 0 时可回收。

java 复制代码
Object obj = new Object(); // 引用计数 = 1
Object ref = obj;          // 引用计数 = 2
ref = null;                // 引用计数 = 1
obj = null;                // 引用计数 = 0 → 可回收

问题:循环引用导致计数永远不为 0。

java 复制代码
Object a = new Object();
Object b = new Object();
a.ref = b;
b.ref = a;
a = null;
b = null;
// 引用计数都是 1,但实际已无法访问 → 内存泄漏

1.2 可达性分析(Java 采用)

从 GC Roots 开始向下搜索,搜索过的路径称为引用链。如果一个对象到 GC Roots 没有任何引用链,说明不可达,可回收。

复制代码
GC Roots(根节点集合)
├── 虚拟机栈中的引用(局部变量)
├── 方法区中静态属性引用
├── 方法区中常量引用
├── 本地方法栈中 JNI 引用
└── 被同步锁持有的对象

二、四种垃圾回收算法

2.1 标记-清除(Mark-Sweep)

过程

  1. 标记所有需要回收的对象

  2. 统一清除被标记的对象

    回收前:[A][B][C][D][E] (B、D 是垃圾)
    标记后:[A][✗][C][✗][E]
    清除后:[A][ ][C][ ][E] ← 内存不连续

优点 :简单

缺点 :产生大量不连续的内存碎片,后续分配大对象时可能找不到连续空间

适用:老年代(对象存活率高)

2.2 复制(Copying)

过程

  1. 将内存分为大小相等的两块

  2. 每次只使用一块

  3. 回收时,将存活对象复制到另一块

  4. 清空当前使用的块

    回收前:
    From 区:[A][B][C][D] (B、D 是垃圾)
    To 区:[ ][ ][ ][ ]

    复制后:
    From 区:[ ][ ][ ][ ] ← 清空
    To 区:[A][C][ ][ ] ← 存活对象

优点 :无内存碎片,分配效率高(指针碰撞)

缺点:浪费一半内存

改进(实际采用):Eden + 两个 Survivor(8:1:1)

  • Eden 占 80%,Survivor 各占 10%
  • 每次使用 Eden + 一个 Survivor
  • 回收时,将存活对象复制到另一个 Survivor
  • 只浪费 10% 内存

适用:新生代(对象朝生夕死,存活率低)

2.3 标记-整理(Mark-Compact)

过程

  1. 标记所有需要回收的对象

  2. 将存活对象向一端移动

  3. 清理边界外的内存

    回收前:[A][B][C][D][E] (B、D 是垃圾)
    标记后:[A][✗][C][✗][E]
    整理后:[A][C][E][ ][ ] ← 内存连续

优点 :无内存碎片

缺点 :移动对象开销大(需要更新引用)

适用:老年代(对象存活率高,移动成本低)

2.4 分代回收(Generational Collection)

核心思想:根据对象存活年代选择不同的算法。

区域 对象特点 采用算法
新生代 朝生夕死,存活率低 复制算法(效率高)
老年代 存活率高,移动成本大 标记-清除或标记-整理

面试回答模板:分代回收是为了提高效率。新生代对象大部分都死,用复制算法每次只复制少量存活对象;老年代对象存活率高,用标记-整理避免碎片。


三、CMS 回收器(Concurrent Mark Sweep)

3.1 特点

  • 目标:最短回收停顿时间
  • 算法:标记-清除
  • 适用:老年代
  • 并发回收:回收过程中用户线程继续运行

3.2 四个阶段

复制代码
1. 初始标记(Initial Mark)
   └─ 标记 GC Roots 直接关联的对象
   └─ STW(Stop The World,暂停用户线程)
   └─ 速度快

2. 并发标记(Concurrent Mark)
   └─ 从 GC Roots 开始遍历整个对象图
   └─ 用户线程并发执行
   └─ 耗时最长

3. 重新标记(Remark)
   └─ 修正并发标记期间变动的对象
   └─ STW
   └─ 时间比初始标记长

4. 并发清除(Concurrent Sweep)
   └─ 清除被标记的垃圾对象
   └─ 用户线程并发执行

3.3 优缺点

优点

  • 并发收集、低停顿
  • 适合互联网应用(对响应时间敏感)

缺点

问题 原因 解决
CPU 资源敏感 并发阶段占用 CPU -XX:ParallelGCThreads 控制线程数
浮动垃圾 并发清除阶段新产生的垃圾 预留内存空间
内存碎片 标记-清除算法 Full GC 时整理
并发失败 内存不足 → Full GC → STW 调大堆或换 G1

四、G1 回收器(Garbage First)

4.1 特点

  • 目标:可预测的停顿时间模型
  • 算法:标记-整理(整体)+ 复制算法(局部)
  • 适用:全堆(新生代 + 老年代)
  • JDK 9 成为默认回收器

4.2 核心设计:Region 划分

G1 把堆划分为多个大小相等的独立区域(Region):

复制代码
堆(Heap)
┌─────┬─────┬─────┬─────┬─────┐
│ Eden│ Eden│ S0  │ S1  │ Old │
├─────┼─────┼─────┼─────┼─────┤
│ Old │ Old │ Hum │ Hum │ Old │
├─────┼─────┼─────┼─────┼─────┤
│ Old │ Old │ Old │ Old │ Old │
└─────┴─────┴─────┴─────┴─────┘

Region 类型:
- Eden:新生代对象
- Survivor:存活对象
- Old:老年代对象
- Hum:Humongous(大对象,超过 Region 50%)

关键点:每个 Region 既可能是新生代,也可能是老年代,不再物理隔离。

4.3 回收过程

复制代码
1. 初始标记(STW)
   └─ 标记 GC Roots 直接关联的对象

2. 并发标记
   └─ 遍历对象图,统计各 Region 存活对象价值

3. 最终标记(STW)
   └─ 修正并发标记期间变动

4. 筛选回收
   └─ 根据用户设定的停顿时间,选择回收价值最高的 Region
   └─ 复制存活对象,清空垃圾 Region

核心思想:优先回收垃圾最多的 Region(Garbage First 名字的由来)。

4.4 CMS vs G1 对比

维度 CMS G1
适用区域 老年代 全堆
算法 标记-清除 标记-整理 + 复制
内存碎片 无(整理过)
停顿预测 不支持 支持(-XX:MaxGCPauseMillis
适用场景 小堆(<8G)、低延迟 大堆(≥8G)、可预测停顿

🗺️ 思维导图速览

复制代码
JVM 垃圾回收
├── 判断对象存活
│   ├── 引用计数法(循环引用问题)
│   └── 可达性分析(GC Roots)
├── 四种算法
│   ├── 标记-清除(碎片问题,老年代)
│   ├── 复制(新生代,Eden+S0+S1)
│   ├── 标记-整理(老年代,无碎片)
│   └── 分代回收(新生代复制+老年代标记-整理)
└── 回收器
    ├── CMS(并发标记清除,低延迟,有碎片,适合小堆)
    └── G1(Region 划分,可预测停顿,JDK 9+ 默认,适合大堆)

📝 写在最后

学习建议

  1. GC 算法要理解区别:标记-清除有碎片、复制浪费内存、标记-整理移动开销大,面试会问"为什么新生代用复制、老年代用标记-整理"
  2. CMS 和 G1 要对比:CMS 适合小堆低延迟但有碎片,G1 Region 划分可预测停顿无碎片
  3. JDK 9+ 默认 G1:这个结论要记住,面试经常问

面试回答模板

Q:说说 JVM 的垃圾回收算法?

JVM 有四种垃圾回收算法。标记-清除算法简单但会产生内存碎片;复制算法把内存分成两块,每次只使用一块,回收时把存活对象复制到另一块,适合新生代;标记-整理算法先标记再移动存活对象,无碎片但移动开销大,适合老年代;分代回收结合两者优点,新生代用复制算法,老年代用标记-清除或标记-整理。

Q:CMS 和 G1 的区别?

CMS 是并发标记清除回收器,适合老年代,追求低延迟但会产生内存碎片;G1 是 Garbage First 回收器,把堆划分为多个 Region,整体看是标记-整理,局部是复制算法,支持可预测的停顿时间模型,JDK 9 成为默认回收器。CMS 适合小堆(<8G),G1 适合大堆(≥8G)。

相关推荐
蓝胖的四次元口袋1 小时前
JVM知识梳理(3)
jvm
磁爆步兵1 天前
内存分区:程序运行的核心秘密
java·开发语言·jvm
讓丄帝愛伱1 天前
JVM线上故障快速排
jvm
落苜蓿蓝1 天前
Java 循环中对象复用导致属性覆盖?从 JVM 内存模型讲解原因
java·jvm·python
一只专注api接口开发的技术猿1 天前
OpenClaw 实战:搭建京东商品监控与简易数据分析脚本
大数据·jvm·数据库·oracle·数据分析
Tim_102 天前
【C++】021、RAII
jvm
格兰芬多呼神护卫2 天前
# Agent 企业评估方案深度分析:从“看答案”到持续质量闭环_码士教育
java·开发语言·jvm
CodeStats2 天前
【Java GC】Java JVM 垃圾回收(GC)完全指南:从内存结构到并发回收的底层原理
java·jvm·gc·垃圾回收·zgc·内存结构
ChaHae-In2 天前
JVM运行时数据区与垃圾回收
jvm