JVM 垃圾回收(GC)笔记

发布于:2025-07-01 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、为什么需要垃圾回收(GC)

  • Java 程序运行过程中会不断创建对象,堆内存有限。
  • 无法被引用的对象占用空间,如果不释放,会导致 内存泄漏或内存溢出(OOM)
  • 手动管理内存易出错,Java 引入自动垃圾回收机制。

二、JVM 中的内存区域(重点)

区域 说明
程序计数器 每个线程私有,记录字节码执行位置
虚拟机栈 每个线程私有,保存局部变量表
本地方法栈 调用 native 方法时使用
堆(Heap) 所有线程共享,垃圾收集的主要区域,保存对象实例
方法区(元空间) 保存类信息、常量、静态变量等

三、如何判断对象是否可以被回收?

引用计数法(Reference Counting)

  • 每个对象维护一个引用计数器;
  • 计数为 0 → 可回收;
  • 缺陷:无法处理循环引用。

可达性分析法(Reachability Analysis)(Java 使用)

  • GC Root 出发向外遍历:

    • 方法区静态变量
    • 方法栈局部变量
    • JNI 引用
  • 如果一个对象无法从 GC Root 访问到,则被判定为不可达 → 可回收对象


四、对象的生命周期与回收区域

区域 描述
新生代(Young Generation) 包括 Eden + 两个 Survivor 区域(S0、S1)
老年代(Old Generation) 存放长时间存活的对象
元空间(Metaspace) Java 8 后替代永久代,存类元数据

五、常见垃圾回收算法

复制算法(Copying)

  • 应用于新生代

  • 将 Eden 中的存活对象复制到 Survivor 区;

  • 一般为 Eden + From(S0)+ To(S1):

    • 每次使用其中两个,另一个为空;
  • 优点:没有碎片,效率高;

  • 缺点:内存浪费严重(只用了一半)。


标记-清除算法(Mark-Sweep)

  • 应用于老年代

  • 第一步:标记可达对象;

  • 第二步:清除未被标记对象;

  • 缺点

    • 产生内存碎片;
    • 清除速度慢。

标记-压缩(整理)算法(Mark-Compact)

  • 是标记清除的改进;
  • 标记完后把存活对象压缩到一端,释放内存;
  • 避免内存碎片问题,适用于老年代。

分代收集(Generational GC)

  • 新生代使用复制算法老年代使用标记清除或压缩算法

  • 理由:

    • 大多数对象“朝生夕死”,适合复制算法;
    • 老年代对象存活率高,复制开销大,适合标记压缩。

六、常见垃圾收集器

收集器名称 作用区域 特点
Serial 新生代 单线程,Stop-The-World,适合单核/小应用
ParNew 新生代 Serial 的多线程版本
Parallel Scavenge 新生代 吞吐量优先,适合后台批处理系统
CMS(已废弃) 老年代 并发收集、低停顿,但容易产生碎片
Serial Old 老年代 Serial 的老年代版本
Parallel Old 老年代 Parallel 的老年代版本
G1(推荐) 新+老 面向服务端,低延迟、可预测停顿
ZGC / Shenandoah 新+老 超低延迟,适合超大内存(100GB+)应用

七、GC 类型与触发时机

GC 类型 触发时机 回收区域 使用算法
Minor GC 新生代满 新生代 复制算法
Major GC 老年代满 老年代 标记-压缩
Full GC 整体内存压力高、元空间满、System.gc() 新生代 + 老年代 + 元空间 综合算法

八、如何选择垃圾收集器?

单核、资源紧张、客户端程序:

  • 使用 Serial + Serial Old

多核、后台任务型应用、对吞吐量要求高:

  • 使用 Parallel Scavenge + Parallel Old

用户体验要求高、低延迟应用:

  • 使用 G1(JDK9 后默认)、ZGCShenandoah

九、G1 收集器简要说明(面试重点)

  • 将堆划分为多个 Region;
  • 并发执行 Mark 阶段,减少 STW;
  • 优先回收垃圾最多的 Region(Garbage First);
  • 可通过 -XX:MaxGCPauseMillis 控制最大停顿时间;
  • 对大内存友好,JDK9+ 推荐默认使用。

十、调试与配置常用参数

# 查看 GC 日志(JDK8)
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

# 设置新生代大小
-Xmn256m

# 设置堆最大/最小值
-Xms512m -Xmx512m

# 使用 G1 收集器
-XX:+UseG1GC

# 控制最大 GC 停顿时间
-XX:MaxGCPauseMillis=200

十一、垃圾回收相关工具

工具名称 说明
jstat 查看 JVM 各种运行指标(GC、类加载、堆等)
jvisualvm 图形化 JVM 监控工具
jconsole 图形化管理工具,可连远程
GCViewer 分析 GC 日志
MAT(Memory Analyzer Tool) 堆内存分析工具,找内存泄漏

十二、面试高频问题总结

Q1:对象从新生代晋升到老年代的条件?

  • 经历 -XX:MaxTenuringThreshold 次 GC;
  • Survivor 区放不下时,直接进入老年代;
  • Survivor 区年龄分布达到阈值,提前晋升。

Q2:Minor GC 和 Full GC 有什么区别?

  • Minor GC 只回收新生代,频率高,速度快;
  • Full GC 回收整个堆(新生代 + 老年代 + 元空间),频率低,STW 时间长,影响性能。

Q3:如何判断是否发生内存泄漏?

  • GC 后对象仍然没有被释放;
  • 使用 jmap + MAT 工具分析对象引用链。

总结

JVM 垃圾回收的核心是“分代 + 区域 + 策略”,通过合适的算法(复制、标记清除、压缩)和收集器(如 G1、ZGC)实现不同场景下的高效内存管理,是高性能 Java 应用的保障。

十三、三色标记法(Tri-Color Marking)


三色标记法是什么?

三色标记法 是现代垃圾回收器中用于并发标记阶段的一种对象可达性追踪算法。其核心目的是在应用线程运行过程中,仍能安全、准确地标记存活对象,避免漏标或误回收。

它被广泛应用于:

  • CMS
  • G1
  • ZGC
  • Shenandoah 等收集器的并发 GC 实现中

三种颜色含义

颜色 状态说明
白色 初始状态,表示对象未被访问;最终仍为白的对象将被回收
灰色 对象已被访问,但其引用的其他对象尚未全部扫描完毕
黑色 对象已访问完毕,自身和引用对象都已处理,判定为存活

在这里插入图片描述

标记流程

  1. 所有对象初始为白色;

  2. GC Root 集合作为入口,加入灰色集合;

  3. 循环处理灰色对象:

    • 标记为黑;
    • 扫描其引用的白色对象 → 转为灰色;
  4. 灰色集合为空后:

    • 剩余未访问的白色对象即为不可达对象 → 回收。

并发 GC 中的“漏标”问题

在并发标记时,用户线程仍在运行,可能会导致以下情况:

  • 对象 A 被标记为黑;
  • A 指向对象 B(白色);
  • 此时用户线程将 A → B 的引用断开,并把 C(灰)指向 B;
  • 后续 GC 先处理了 A(黑),再处理 C(灰)时不再追踪 B;
  • B 由于“断开”后未再被扫描,最终仍为白色 → 被错误回收(漏标)!

解决办法

现代 GC 通常通过以下机制解决漏标问题:

技术 描述说明
增量更新(Incremental Update) 当黑对象新增引用白对象时,重新将白对象置为灰色,等待重新扫描
SATB(Snapshot At The Beginning) 保留标记开始时的对象引用快照,即使中途引用断了,也按初始快照追踪对象存活性

应用收集器

GC 收集器 是否使用三色标记法 附加机制
CMS 增量更新
G1 增量更新
ZGC SATB
Shenandoah SATB

三色标记法通过黑 / 灰 / 白三种状态动态追踪对象引用,确保在并发标记时依然准确地识别出所有存活对象,是现代低停顿 GC 的基础核心算法。


网站公告

今日签到

点亮在社区的每一天
去签到