在 Java 虚拟机(JVM)的垃圾回收体系中,垃圾收集器扮演着至关重要的角色,它们负责自动回收不再使用的内存空间,以确保 JVM 的高效运行。不同的垃圾收集器具有不同的特点和适用场景,了解它们的工作原理和特性,有助于我们根据应用程序的需求选择最合适的垃圾收集器。下面将详细介绍 JVM 中的各种收集器。
新生代收集器
Serial 收集器
- 工作方式:Serial 收集器是最基本、最古老的新生代收集器,它采用单线程的方式进行垃圾回收。在进行垃圾回收时,它会暂停所有的用户线程,直到垃圾回收完成。这种收集器的优点是实现简单,没有线程交互的开销,因此在单线程环境下性能表现较好。
- 适用场景:适用于客户端应用程序或者硬件资源有限的环境,例如在一些小型的桌面应用程序中,由于其对性能要求不是特别高,且硬件资源相对较少,使用 Serial 收集器可以减少系统的开销。
ParNew 收集器
- 工作方式:ParNew 收集器是 Serial 收集器的多线程版本,它使用多个线程同时进行垃圾回收,从而提高了垃圾回收的效率。在进行垃圾回收时,同样会暂停所有的用户线程。与 Serial 收集器相比,ParNew 收集器在多核心处理器的环境下能够充分利用 CPU 资源,加快垃圾回收的速度。
- 适用场景:通常与老年代的 CMS 收集器配合使用,因为 CMS 收集器无法与 Serial 收集器配合工作,而 ParNew 收集器可以作为 CMS 收集器在新生代的搭档。在一些对响应时间要求较高的服务器端应用程序中,ParNew + CMS 的组合是比较常见的选择。
Parallel Scavenge 收集器
- 工作方式:Parallel Scavenge 收集器也是一个新生代的多线程收集器,它的目标是达到一个可控制的吞吐量。吞吐量是指运行用户代码的时间与总运行时间(运行用户代码时间 + 垃圾收集时间)的比值。Parallel Scavenge 收集器提供了两个重要的参数来控制吞吐量,-XX:MaxGCPauseMillis用于控制最大垃圾收集停顿时间,-XX:GCTimeRatio用于直接设置吞吐量大小。它在进行垃圾回收时,同样会暂停用户线程。
- 适用场景:适用于对吞吐量要求较高的应用程序,例如一些后台计算任务、大数据处理等场景。在这些场景中,应用程序更关注的是整体的处理能力,而对响应时间的要求相对较低,因此 Parallel Scavenge 收集器能够充分发挥其优势。
老年代收集器
Serial Old 收集器
- 工作方式:Serial Old 收集器是 Serial 收集器的老年代版本,它同样采用单线程的方式进行垃圾回收,在垃圾回收时会暂停所有用户线程。它主要用于在 Client 模式下的虚拟机,或者作为 CMS 收集器发生 Concurrent Mode Failure 后的后备预案,在并发收集失败时使用 Serial Old 收集器进行垃圾回收,以保证系统的正常运行。
- 适用场景:与 Serial 收集器类似,适用于硬件资源有限的环境或者作为 CMS 收集器的后备方案。
Parallel Old 收集器
- 工作方式:Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,它使用多线程进行垃圾回收,并且以高吞吐量为目标。在 JDK 1.6 之前,如果新生代使用 Parallel Scavenge 收集器,老年代只能使用 Serial Old 收集器,这在一定程度上限制了应用程序的性能。JDK 1.6 之后引入了 Parallel Old 收集器,使得 Parallel Scavenge 收集器在老年代也能有更好的性能表现,实现了真正意义上的高吞吐量的垃圾回收组合。
- 适用场景:与 Parallel Scavenge 收集器搭配使用,适用于对吞吐量要求较高的应用程序,特别是在服务器端应用中,能够充分利用多核心处理器的优势,提高系统的整体性能。
CMS(Concurrent Mark Sweep)收集器
- 工作方式:CMS 收集器是一种以获取最短回收停顿时间为目标的收集器,它非常适合用于对响应时间要求较高的应用程序,例如 Web 应用程序。CMS 收集器的工作过程相对复杂,主要分为以下几个阶段:
- 初始标记(Initial Mark):暂停所有用户线程,标记出 GC Roots 能直接关联到的对象,这个阶段的速度很快,但会产生短暂的停顿。
- 并发标记(Concurrent Mark):与用户线程并发执行,从 GC Roots 的直接关联对象开始遍历整个对象图,标记出所有可达对象。这个阶段不会暂停用户线程,但会占用一部分 CPU 资源。
- 重新标记(Remark):暂停所有用户线程,修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间通常比初始标记稍长,但比并发标记短。
- 并发清除(Concurrent Sweep):与用户线程并发执行,清除标记为不可达的对象,释放内存空间。
- 适用场景:适用于对响应时间要求较高的应用程序,如 Web 服务器、大型企业级应用等。这些应用程序需要快速响应用户的请求,因此 CMS 收集器能够在尽量减少停顿时间的情况下完成垃圾回收工作,提高用户体验。
三、混合收集器
G1(Garbage-First)收集器
- 工作方式:G1 收集器是一种面向服务器的垃圾收集器,它将整个 Java 堆划分为多个大小相等的独立区域(Region),虽然 G1 仍然是分代收集器,但它不再是简单地将堆分为新生代和老年代,而是将堆内存划分为多个 Region,每个 Region 可以根据需要扮演新生代或者老年代的角色。G1 收集器的工作过程主要包括以下几个阶段:
- 初始标记(Initial Mark):暂停所有用户线程,标记出 GC Roots 能直接关联到的对象,与 CMS 收集器的初始标记类似。
- 并发标记(Concurrent Mark):与用户线程并发执行,从 GC Roots 开始对堆中对象进行可达性分析,标记出存活对象,这个阶段会占用一部分 CPU 资源。
- 最终标记(Final Mark):暂停用户线程,处理并发标记阶段结束后仍遗留的少量对象的标记记录。
- 筛选回收(Live Data Counting and Evacuation):根据每个 Region 中垃圾回收的收益(回收的内存大小和所需的时间)进行排序,优先回收收益最大的 Region。在这个阶段,会将存活对象复制到其他 Region 中,同时释放被回收的 Region 的内存空间。
- 适用场景:适用于大内存、多处理器的服务器环境,尤其是对应用程序的停顿时间有严格要求的场景。G1 收集器能够在满足停顿时间目标的前提下,尽可能地提高吞吐量,并且可以有效地处理大内存的垃圾回收问题。例如,在一些大型的电商平台、金融交易系统等对性能和稳定性要求极高的应用中,G1 收集器得到了广泛的应用。
JVM 中的各种收集器都有其独特的特点和适用场景。在实际应用中,我们需要根据应用程序的类型、硬件环境以及对性能的要求等因素,综合选择合适的垃圾收集器,以确保 JVM 的高效运行和应用程序的稳定性能。