垃圾收集器分类
垃圾收集器可以从不同维度分类:
按线程数:
串行(Serial):单线程执行垃圾收集。
并行(Parallel):多线程并行执行垃圾收集。
按工作模式:
独占式:执行 GC 时,暂停所有应用线程(Stop-The-World, STW)。
并发式:大部分 GC 工作与应用线程并发执行,只在某些阶段需要 STW。
按碎片处理:
压缩式:在回收后会对存活对象进行压缩整理,消除内存碎片。
非压缩式:回收后不整理,会产生内存碎片。
复制式:将存活对象复制到另一块内存区域,本身也是一种整理。
经典的垃圾收集器(JDK 8 及之前)
以下是 Java 8 及之前版本中经典的、可选的垃圾收集器,它们通常采用分代收集策略(将堆分为新生代和老年代)。
1. Serial 收集器(串行)
特点:最古老、最基础的单线程收集器。进行垃圾收集时,必须暂停所有工作线程(STW),直到收集结束。
适用区域:新生代(采用复制算法)、老年代(采用标记-整理算法)。
适用场景:客户端模式下的默认收集器。适用于内存资源受限、单核处理器或对停顿不敏感的场景(如桌面应用、小型应用)。
优点:简单高效,没有线程交互开销。
缺点:STW 时间较长。
2. Parallel Scavenge / Parallel Old 收集器(吞吐量优先)
特点:JDK 8 的默认收集器。关注点是可达到的吞吐量(吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。
新生代(Parallel Scavenge):多线程并行收集,使用复制算法。
老年代(Parallel Old):多线程并行收集,使用标记-整理算法。
适用场景:适合后台运算、科学计算、批量处理等不需要太多交互、追求高吞吐量的任务。
优点:吞吐量高。
缺点:STW 时间依然较长,不适合需要低延迟响应的应用。
3. ParNew 收集器
特点:本质是 Serial 收集器的多线程并行版本。除了使用多线程进行垃圾收集外,其余行为与 Serial 收集器完全一样。
适用区域:主要是新生代(采用复制算法)。
重要搭档:它是CMS 收集器在新生代的默认搭档。
适用场景:主要与 CMS 搭配使用,用于新生代的垃圾回收。
4. CMS(Concurrent Mark-Sweep)收集器(低延迟优先)
特点:以获取最短回收停顿时间为目标的收集器。大部分垃圾收集工作与用户线程并发执行。
适用区域:老年代(采用标记-清除算法,会产生内存碎片)。
执行过程(四个步骤):
初始标记(STW):标记 GC Roots 能直接关联到的对象。速度很快。
并发标记:从 GC Roots 开始进行可达性分析,与用户线程并发执行。
重新标记(STW):修正并发标记期间因用户程序继续运作而导致标记产生变动的那部分对象的标记记录。
并发清除:清理垃圾对象,与用户线程并发执行。
优点:并发收集,停顿时间短。
缺点:
- 对 CPU 资源敏感,会占用一部分线程导致应用程序变慢。
- 无法处理“浮动垃圾”,可能在并发清理阶段产生新的垃圾。
- 使用标记-清除算法,会产生内存碎片,可能触发 Full GC。
5. G1(Garbage-First)收集器(全功能型)
特点:面向服务端应用的垃圾收集器,是 JDK 9 及以后的默认收集器。它试图取代 CMS。
革命性变化:它将堆内存划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但它们都是Region的集合,不再是物理隔离。
核心思想:G1 跟踪各个 Region 里面的垃圾堆积的“价值”大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(“Garbage-First”名字的由来)。
执行过程(四个步骤,与CMS类似但更先进):
初始标记(STW)
并发标记
最终标记(STW)
筛选回收(STW):根据用户期望的停顿时间来制定回收计划,将决定回收的 Region 中的存活对象复制到空的 Region 中,再清空整个旧的 Region。这是一个并行压缩的过程,有效避免了碎片。
优点:
能充分利用多核CPU环境,缩短STW时间。
整体上看是标记-整理算法,局部(两个Region之间)看是复制算法,都不会产生内存碎片。
可预测的停顿:可以设置一个期望的停顿时间目标,G1会尽力达成。
如何选择垃圾收集器?
收集器 | 目标 | 适用场景 | JDK 默认情况 |
---|---|---|---|
Serial | 简单高效,减少内存消耗 | 单核CPU、客户端应用、嵌入式系统 | 客户端模式默认 |
Parallel Scavenge/Old | 高吞吐量 | 后台计算、批处理 | JDK 8 服务端模式默认 |
CMS | 低延迟 | Web 服务器、B/S 系统(已逐渐被淘汰) | 需手动启用 |
G1 | 吞吐量与低延迟的平衡 | 服务端应用,堆内存较大(6GB+) | JDK 9+ 默认 |
选择建议:
如果不确定,使用默认值。从 JDK 9 开始,G1 是默认选择,它在大多数情况下表现良好。
如果应用追求高吞吐量(如数据处理),并且可以忍受较长的停顿,可以使用
-XX:+UseParallelGC
。如果应用追求低延迟(如 Web 服务),堆内存较大(如超过 4-6G),选择 G1
-XX:+UseG1GC
。