经典的垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
JDK 默认垃圾收集器(使用 java -XX:+PrintCommandLineFlags -version
命令查看):
- JDK 8: Parallel Scavenge(新生代)+ Parallel Old(老年代)
- JDK 9 ~ JDK22: G1
Serial收集器
Serial的意思是串行 ,显然是一个单线程 模型,它的 "单线程" 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
该收集器中,新生代采用标记-复制算法,老年代采用标记-整理算法 。
- 优点
- 简单高效 :Serial采用复制算法,并且是单线程执行,这使其在单核CPU 的场景中能够专心处理垃圾回收任务,而不用考虑线程间切换的开销,从而表现出较高的收集效率
- 稳定性好:作为最基本、发展历史最悠久的收集器,Serial收集器经过长时间的验证和优化,具有较高的稳定性。
- 缺点
- 单线程收集 :正因为其是单线程的,所以在进行GC时,要进行STW(暂停所有其它的工作线程,直到收集完成),这会导致应用程序在垃圾收集期间无法响应用户请求,这种停顿带来的体验是极差的
- 不适合多核环境:在多核CPU的环境中,Serial收集器无法充分利用多核处理器的优势,其单线程执行的特性会导致收集效率相对较低
- 特点
- 新生代收集器 :Serial收集器主要用于新生代的垃圾收集,是Client模式下的默认新生代收集器。在Client模式下,由于应用程序通常对吞吐量要求不高,而对停顿时间较为敏感,因此Serial收集器的简单高效特性使其成为了一个很好的选择。
- 参数控制 :在Java虚拟机中,可以通过参数
-XX:+UseSerialGC
来显式指定使用Serial垃圾收集器。生效后,新生代使用Serial收集器,老年代则使用Serial Old收集器
综上所述,Serial收集器具有简单高效、稳定性好等优点,但同时也存在单线程收集、不适合多核环境等缺点。在选择垃圾收集器时,需要根据应用程序的具体需求和运行环境进行权衡和选择.
ParNew收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本 ,除了使用多线程进行垃圾收集外,其余行为(控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器配合工作。
- 优点:
- 多线程并行收集:ParNew收集器采用多线程并行的方式进行垃圾收集,能够充分利用多核CPU的并行处理能力,提高垃圾收集的效率。
- 高吞吐量:通过多线程并行收集,ParNew收集器能够减少垃圾收集的时间,从而在一定程度上提高了应用程序的吞吐量。
- 与CMS收集器搭配使用 :ParNew收集器通常与CMS(Concurrent Mark-Sweep)收集器搭配使用,CMS收集器负责老年代 的垃圾收集,而ParNew收集器负责新生代的垃圾收集。这种搭配能够为Java应用程序提供较低的停顿时间和较高的吞吐量。
- 缺点:
- 单CPU环境下效果一般:在单个CPU环境下,由于存在线程交互的开销,ParNew收集器的性能可能并不比Serial收集器更好。
- 并发执行对CPU压力大:在进行垃圾收集时,ParNew收集器会占用多个CPU线程,这可能会对系统的其他任务造成一定的压力。
- 特点:
- 新生代收集器:ParNew收集器是专门设计用于新生代的垃圾收集器,主要负责收集年轻代的Eden区和Survivor区。
- "Stop-the-World"机制:与Serial收集器类似,ParNew收集器在垃圾收集过程中也会采用**"Stop-the-World"机制**,即暂停所有应用程序线程以进行垃圾收集。不过,由于ParNew收集器是多线程的,因此其停顿时间通常会比Serial收集器更短。
综上所述,ParNew收集器具有多线程并行收集、高吞吐量以及与CMS收集器搭配使用 等优点,但同时也存在单CPU环境下效果一般、并发执行对CPU压力大以及采用标记-清除算法导致空间碎片等缺点。在选择垃圾收集器时,需要根据应用程序的具体需求和运行环境进行权衡和选择。
Parallel Scavenge收集器(吞吐量优先收集器)
Parallel Scavenge 收集器也是使用标记-复制算法 的多线程收集器。
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU) 。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Serial Old收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
Parallel Old收集器
Parallel Scavenge 收集器的老年代版本 。使用多线程和"标记-整理"算法。在注重吞吐量以及 CPU 资源的 场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器 ,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep 这两个词可以看出,CMS 收集器是一种 "标记-清除"算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性 。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
- 优点:
- 并发收集:CMS收集器能够与用户线程并发执行,这意味着在垃圾收集过程中,用户线程不会被长时间阻塞,从而提高了应用程序的响应性和吞吐量。
- 低停顿:由于CMS收集器采用并发收集的方式,因此可以显著降低垃圾收集过程中的停顿时间,这对于需要高实时性响应的应用程序尤为重要。
- 缺点:
- **对 CPU 资源敏感:**CMS收集器在并发阶段会占用一定的CPU资源,这可能会导致应用程序的吞吐量降低。虽然虚拟机提供了"增量式并发收集器"等变种来减少GC线程独占资源的时间,但效果并不明显,且可能导致垃圾收集过程变长。
- 无法处理浮动垃圾:CMS收集器在并发清理阶段,用户线程仍在运行,因此会产生新的垃圾,这些垃圾被称为"浮动垃圾",进而导致另一次完全"Stop The World"的Full GC的产生
- 它使用的回收算法-"标记-清除"算法会导致收集结束时会有大量空间碎片产生:CMS收集器是基于"标记-清除"算法实现的,这种算法在收集结束时会产生大量的内存碎片。当碎片过多时,可能会导致大对象无法分配,从而触发Full GC。虽然CMS提供了一个开关参数用于在必要时开启内存碎片的合并整理过程,但这个过程是无法并发的,会导致停顿时间变长。
G1收集器
G1 (Garbage-First) 是一款面向服务器 的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间 要求的同时,还具备高吞吐量性能特征.
它开创了收集 器面向局部收集的设计思路和基于Region的内存布局形式
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的"标记-清除"算法不同,G1 从整体来看是基于"标记-整理"算法实现的收集器;从局部上来看是基于"标记-复制"算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。
G1 收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1 收集器在后台维护了一个优先列表 ,每次根据允许的收集时间 ,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
从 JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器