JVM垃圾回收器

53 阅读6分钟

在这里插入图片描述

串行垃圾回收器

在这里插入图片描述 -XX:+UseSerialGC = Serial + SerialOld -XX:+UseSerialGC 代表使用 Serial + SerialOld 的组合:

Serial → 负责新生代(Young Generation)垃圾回收

SerialOld → 负责老年代(Old Generation)垃圾回收

它是 最基础的垃圾回收器,所有操作都是单线程执行的。

执行流程

当 Serial GC 启动时:

所有用户线程(蓝线)都会在安全点被 暂停(阻塞);

仅有一个 GC 线程(红线)在工作,执行回收任务。

在此期间,程序完全停止,只有垃圾回收在运行。

这也是**“串行(Serial)”**名称的由来——只有一个线程负责 GC 操作。

垃圾回收算法

新生代:使用 复制算法(Copying)

存活对象从 Eden + SurvivorFrom → SurvivorTo;

清空 Eden 区;

老年代:使用 标记-整理算法(Mark-Compact)

标记存活对象;

整理内存以减少碎片。

吞吐量优先

在这里插入图片描述

Parallel GC

-XX:+UseParallelGC -XX:+UseParallelOldGC 这表示:

新生代(Young Generation) 使用 Parallel Scavenge GC;

老年代(Old Generation) 使用 Parallel Old GC 在这里插入图片描述 -XX:GCTimeRatio-n -> GC时间占总时间比例 1 / 1 + ratio

和 Serial GC 最大的不同在于:

在安全点(Safe Point)之后,Parallel GC 会同时启动多个垃圾回收线程(红线)。

执行流程

应用运行阶段(蓝线) 多线程并行执行业务逻辑。

进入安全点(Safe Point) JVM 触发 GC,暂停所有应用线程(Stop-The-World)。

并行回收阶段(红线) 多个 GC 线程并行执行标记、复制、清理操作。

新生代采用 复制算法(Copying);

老年代采用 标记-整理算法(Mark-Compact)。

回收完成 → 应用恢复(蓝线) 所有垃圾回收线程结束,应用线程恢复执行。

在这里插入图片描述

响应时间优先

在这里插入图片描述

CMS

-XX:+UseConcMarkSweepGC 这会启用:

新生代:ParNew(并行复制算法)

老年代:CMS(并发标记清除算法)

如果 CMS 失败,则回退使用 SerialOld。

💡 CMS 的目标:尽量让 GC 与应用线程同时执行,减少长时间停顿。

在这里插入图片描述

⚠️CMS 的主要问题


🧱 1️⃣ 内存碎片问题(最致命问题)

CMS 使用 标记-清除算法(Mark-Sweep)

  • 它只清除“死亡对象”,不整理内存空间
  • 结果是——产生大量的 内存碎片
📉 影响:
  • 大对象(需要连续内存空间)可能无法分配;

  • 虽然老年代还有很多空闲内存,但分配失败会触发:

    Concurrent Mode Failure
    

    然后 JVM 被迫使用 Serial Old GC(单线程、Stop-The-World) 进行整理,造成严重的长时间停顿

💬 比喻:

CMS 就像清理房间时只是“把垃圾扫走”,但没整理地毯、没叠被子,结果空间虽大但不连贯。


🕳️ 2️⃣ “Concurrent Mode Failure” 并发模式失败

这个错误非常典型,说明:

CMS 在并发清理的过程中,应用线程仍在分配对象,结果老年代很快被填满,GC 还没清完垃圾就被逼停下。

于是:

  • JVM 触发 Full GC(Serial Old)
  • 所有线程暂停;
  • 延迟陡增。
📘 解决方向:
  • 提前触发 CMS:

    -XX:CMSInitiatingOccupancyFraction=70
    
  • 保留足够的空闲空间让应用分配;

  • 或者直接改用 G1 GC。


⚙️ 3️⃣ “浮动垃圾(Floating Garbage)” 问题

CMS 的标记与清除是并发执行的。
在并发标记阶段,应用线程仍在创建和销毁对象。
因此:

  • 有些在标记阶段创建、并在清理前销毁的对象无法被识别为垃圾
  • 它们会暂时留到下一次 GC 才能清除;
  • 这部分就是所谓的 “浮动垃圾”
📉 影响:
  • 降低内存利用率;
  • 加大下一次 GC 的压力。

🧮 4️⃣ CPU 资源竞争(并发阶段消耗性能)

CMS 的并发阶段与应用线程同时执行。
这意味着:

  • GC 线程和业务线程 同时争夺 CPU 资源
  • 如果 CPU 核心数不多,应用性能可能下降;
  • 延迟虽然低,但整体吞吐量下降。
📘 举例:

在 4 核机器上,如果 CMS 开启 2 个 GC 并发线程,那么业务线程可用的 CPU 就只剩一半。


📊 5️⃣ GC 周期复杂、调优困难

CMS 的触发时机和线程配置都需要手动调优,参数多且相互影响:

  • 触发比例:

    -XX:CMSInitiatingOccupancyFraction=70
    
  • 并发线程数:

    -XX:ConcGCThreads
    
  • 重新标记优化:

    -XX:+CMSScavengeBeforeRemark
    

若配置不当:

  • 可能太早触发 GC,浪费资源;
  • 也可能太晚,导致 Concurrent Mode Failure;
  • 难以预测停顿时间。

🧯 6️⃣ 无法处理巨堆(大内存)场景

CMS 的并发标记和清理算法在大堆(>8GB)场景下效率会明显下降:

  • 标记范围太大;
  • 线程切换成本上升;
  • 对延迟和吞吐量的平衡越来越难控制。

因此,从 JDK 9 开始默认启用 G1
JDK 14 起,CMS 已被正式标记为废弃(Deprecated)


🧠 三、CMS 的问题总结表

问题描述影响解决方案
内存碎片标记-清除不整理内存分配失败、Full GC换用 G1 / 调整堆
并发模式失败GC 还没清完就满了触发 Full GC提前触发 CMS
浮动垃圾并发标记期间产生的新垃圾无法清理占用内存下次 GC 再清
CPU 竞争GC 线程与业务线程抢资源吞吐量下降增加 CPU 核数
调优复杂参数多、互相影响配置困难改用自适应的 G1
不适合大堆标记耗时、碎片多延迟大、效率低使用 G1/ZGC


G1垃圾回收器

image.png

image.png

图中有三个三角形节点,代表 G1 的三种主要垃圾回收阶段

  1. 🟢 Young Collection(年轻代收集)
  2. 🔵 Young Collection + Concurrent Mark(年轻代收集 + 并发标记)
  3. 🟠 Mixed Collection(混合收集)

这三者形成一个循环流程:

Young → Young + Concurrent Mark → Mixed → 回到 Young

三个阶段的含义与关系

image.png

🟢 1️⃣ Young Collection(年轻代收集)

  • 对象首先分配在 Eden 区域。
  • 当 Eden 填满时触发 Young GC
  • 使用 复制算法(Copying)
    Eden → Survivor / Old。
  • 停顿类型:Stop-The-World(STW)
  • 通常是 并行执行,多个 GC 线程同时处理。

📘 特点:

  • 只处理年轻代的 Region;
  • 不涉及老年代;
  • 发生频率高,但停顿短。

image.png

image.png


🔵 2️⃣ Young Collection + Concurrent Mark(年轻代收集 + 并发标记)

当 JVM 监测到老年代使用率超过阈值(通常是 45%)时,
在一次 Young GC 之后,会 启动全堆的并发标记(Concurrent Marking) 阶段。

这个阶段包含两个过程:

  1. 一次普通的 Young GC(STW)
  2. 紧接着开始 并发标记(与应用线程并发执行)。

💡 并发标记的目标:

  • 扫描整个堆中所有 Region;
  • 标记哪些对象是存活的;
  • 统计每个 Region 的“垃圾比例”。

🧭 这样,G1 就能知道——哪些 Region 的“垃圾最多”,
为后面的 Mixed Collection 做准备。

image.png


🟠 3️⃣ Mixed Collection(混合收集)

标记阶段完成后,G1 会执行一系列 Mixed GC

  • 同时回收年轻代和部分老年代的 Region(即“混合”)。
  • 优先清理垃圾比例高的老年代 Region(“Garbage First” 的由来)。
  • 每次回收的 Region 数量有限(由暂停时间目标决定)。
  • 依旧是 STW + 并行回收

📘 Mixed GC 会重复多次,直到老年代回收足够多空间。

之后,回收循环重新开始(回到 Young Collection 阶段)。

image.png

image.png