Choosing a GC Algorithm in Java – 在Java中选择一个GC算法

最后修改: 2021年 8月 10日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

The JVM ships with various options for garbage collection to support a variety of deployment options. With this, we get flexibility in choosing which garbage collector to use for our application.

JVM提供了各种垃圾收集的选项,以支持各种部署选项。有了它,我们就可以灵活地选择哪种垃圾收集器来用于我们的应用程序。

By default, the JVM chooses the most appropriate garbage collector based on the class of the host computer. However, sometimes, our application experiences major GC-related bottlenecks requiring us to take more control over which algorithm is used. The question is, “how does one settle on a GC algorithm?”

默认情况下,JVM会根据主机的等级选择最合适的垃圾收集器。然而,有时候,我们的应用程序会遇到与GC相关的重大瓶颈,这就要求我们对使用哪种算法进行更多的控制。问题是,“如何确定一种GC算法?”

In this article, we attempt to answer that question.

在这篇文章中,我们试图回答这个问题。

2. What Is a GC?

2. 什么是GC?

Java being a garbage-collected language, we are shielded from the burden of manually allocating and deallocating memory to applications. The whole chunk of memory allocated to a JVM process by the OS is called the heap. The JVM then breaks this heap into two groups called generations. This breakdown enables it to apply a variety of techniques for efficient memory management.

Java作为一种囊括式语言,我们可以免去为应用程序手动分配和取消分配内存的负担。操作系统分配给JVM进程的整块内存被称为heap。然后,JVM将这个堆分成两组,称为世代。这种分解使它能够应用各种技术进行有效的内存管理。

The young (Eden) generation is where newly created objects are allocated. It’s usually small (100-500MB) and also has two survivor spaces. The old generation is where older or aged objects are stored — these are typically long-lived objects. This space is much larger than the young generation.

年轻(Eden)一代是新创建的对象被分配的地方。它通常很小(100-500MB),也有两个存活者空间老一代是存储老的或老化的对象的地方 – 这些通常是长寿命的对象。这个空间要比年轻一代大得多。

The collector continuously tracks the fullness of the young generation and triggers minor collections during which live objects are moved to one of the survivor spaces and dead ones removed. If an object has survived a certain number of minor GCs, the collector moves it to the old generation. When the old space is considered full, a major GC happens and dead objects are removed from the old space.

采集器不断地追踪年轻一代的饱满度,并触发小规模的收集,在此期间,活的对象被移到一个幸存者空间,而死的对象则被移除。如果一个对象在一定数量的小规模GC中存活下来,收集器就会把它移到老一代。当旧空间被认为是满的时候,就会发生一次大的GC,死的对象就会从旧空间中被移除。

During each of these GCs, there are stop-the-world phases during which nothing else happens — the application can’t service any requests. We call this pause time.

在每个GCs期间,都有stop-the-world阶段,在此期间没有其他事情发生–应用程序不能为任何请求服务。我们称其为暂停时间

3. Variables to Consider

3.需要考虑的变量

Much as GC shields us from manual memory management, it achieves this at a cost. We should aim to keep the GC runtime overhead as low as possible. There are several variables that can help us decide which collector would best serve our application needs. We’ll go over them in the remainder of this section.

就像GC使我们免于手动管理内存一样,它的实现是有代价的。我们应该尽可能地减少GC运行时的开销。有几个变量可以帮助我们决定哪个收集器最能满足我们的应用需求。我们将在本节的剩余部分对它们进行讨论。

3.1. Heap Size

3.1.堆的大小

This is the total amount of working memory allocated by the OS to the JVM. Theoretically, the larger the memory, the more objects can be kept before collection, leading to longer GC times. The minimum and maximum heap sizes can be set using -Xms=<n> and -Xmx=<m> command-line options.

这是由操作系统分配给JVM的工作内存总量。理论上,内存越大,在收集前可以保留的对象就越多,从而导致GC时间延长。可以使用-Xms=<n>-Xmx=<m>命令行选项来设置最小和最大堆大小。

3.2. Application Data Set Size

3.2.应用数据集大小

This is the total size of objects an application needs to keep in memory to work effectively. Since all new objects are loaded in the young generation space, this will definitely affect the maximum heap size and, hence, the GC time.

这是一个应用程序为了有效工作而需要保留在内存中的对象的总大小。由于所有的新对象都是在年轻的一代空间中加载的,这肯定会影响到最大堆的大小,因此也会影响到GC时间。

3.3. Number of CPUs

3.3.CPU的数量

This is the number of cores the machine has available. This variable directly affects which algorithm we choose. Some are only efficient when there are multiple cores available, and the reverse is true for other algorithms.

这是机器可用的核心数量。这个变量直接影响到我们选择哪种算法。有些算法只有在有多个核心可用时才有效,而其他算法则相反。

3.4. Pause Time

3.4.暂停时间

The pause time is the duration during which the garbage collector stops the application to reclaim memory. This variable directly affects latency, so the goal is to limit the longest of these pauses.

暂停时间是指垃圾收集器停止应用以回收内存的时间。这个变量直接影响到延迟,所以目标是限制这些暂停的最长时间。

3.5. Throughput

3.5.吞吐量

By this, we mean the time processes spend actually doing application work. The higher the application time vs. overhead time spent in doing GC work, the higher the throughput of the application.

我们指的是进程实际进行应用工作的时间。应用时间与做GC工作的开销时间相比越高,应用的吞吐量就越高

3.6. Memory Footprint

3.6.内存足迹

This is the working memory used by a GC process. When a setup has limited memory or many processes, this variable may dictate scalability.

是GC进程使用的工作内存。当一个设置有有限的内存或许多进程时,这个变量可能决定了可扩展性。

3.7. Promptness

3.7.及时性

This is the time between when an object becomes dead and when the memory it occupies is reclaimed. It’s related to the heap size. In theory, the larger the heap size, the lower the promptness as it will take longer to trigger collection.

是指一个对象变成死的时候和它所占用的内存被回收之间的时间。它与堆的大小有关。理论上,堆的大小越大,提示性越低,因为它需要更长的时间来触发收集。

3.8. Java Version

3.8. Java版本

As new Java versions emerge, there are usually changes in the supported GC algorithms and also the default collector. We recommend starting off with the default collector as well as its default arguments. Tweaking each argument has varying effects depending on the chosen collector.

随着新的Java版本的出现,支持的GC算法和默认的收集器通常会有变化。我们建议从默认的收集器和它的默认参数开始。根据所选择的收集器,对每个参数的调整都有不同的效果。

3.9. Latency

3.9.延迟

This is the responsiveness of an application. GC pauses affect this variable directly.

这是一个应用程序的响应能力。GC暂停会直接影响这个变量。

4. Garbage Collectors

4.垃圾收集器

Besides serial GC, all the other collectors are most effective when there’s more than one core available:

除了连续的GC,所有其他的收集器在有一个以上的核心可用时是最有效的。

4.1. Serial GC

4.1.系列GC

The serial collector uses a single thread to perform all the garbage collection work. It’s selected by default on certain small hardware and operating system configurations, or it can be explicitly enabled with the option -XX:+UseSerialGC.

串行收集器使用一个单线程来执行所有的垃圾收集工作。在某些小的硬件和操作系统配置中,它被默认选择,或者可以通过选项-XX:+UseSerialGC明确启用。

Pros:

优点。

  • Without inter-thread communication overhead, it’s relatively efficient.
  • It’s suitable for client-class machines and embedded systems.
  • It’s suitable for applications with small datasets.
  • Even on multiprocessor hardware, if data sets are small (up to 100 MB), it can still be the most efficient.

Cons:

弊端。

  • It’s not efficient for applications with large datasets.
  • It can’t take advantage of multiprocessor hardware.

4.2. Parallel/Throughput GC

4.2.并行/吞吐量GC

This collector uses multiple threads to speed up garbage collection. In Java version 8 and earlier, it’s the default for server-class machines. We can override this default by using the -XX:+UseParallelGC option.

这个收集器使用多个线程来加速垃圾收集。在Java 8和更早的版本中,它是服务器级机器的默认设置。我们可以通过使用-XX:+UseParallelGC选项来覆盖这个默认值。

Pros:

优点。

  • It can take advantage of multiprocessor hardware.
  • It’s more efficient for larger data sets than serial GC.
  • It provides high overall throughput.
  • It attempts to minimize the memory footprint.

Cons:

弊端。

  • Applications incur long pause times during stop-the-world operations.
  • It doesn’t scale well with heap size.

It’s best if we want more throughput and don’t care about pause time, as is the case with non-interactive apps like batch tasks, offline jobs, and web servers.

如果我们想要更多的吞吐量而不关心暂停时间,这是最好的,就像批处理任务、离线作业和网络服务器等非交互式应用的情况。

4.3. Concurrent Mark Sweep (CMS) GC

4.3.并发标记扫描(CMS)GC

We consider CMS a mostly concurrent collector. This means it performs some expensive work concurrently with the application. It’s designed for low latency by eliminating the long pause associated with the full GC of parallel and serial collectors.

我们认为CMS是一个多并发的收集器。这意味着它与应用程序同时执行一些昂贵的工作。它的设计是通过消除与并行和串行收集器的完整GC相关的长时间停顿来实现低延迟的。

We can use the option -XX:+UseConcMarkSweepGC to enable the CMS collector. The core Java team deprecated it as of Java 9 and completely removed it in Java 14.

我们可以使用选项-XX:+UseConcMarkSweepGC来启用CMS收集器。Java核心团队从Java 9开始就废弃了它,并在Java 14中完全删除了它。

Pros:

优点。

  • It’s great for low latency applications as it minimizes pause time.
  • It scales relatively well with heap size.
  • It can take advantage of multiprocessor machines.

Cons:

弊端。

  • It’s deprecated as of Java 9 and removed in Java 14.
  • It becomes relatively inefficient when data sets reach gigantic sizes or when collecting humongous heaps.
  • It requires the application to share resources with GC during concurrent phases.
  • There may be throughput issues as there’s more time spent overall in GC operations.
  • Overall, it uses more CPU time due to its mostly concurrent nature.

4.4. G1 (Garbage-First) GC

4.4.G1(垃圾优先)GC

G1 uses multiple background GC threads to scan and clear the heap just like CMS. Actually, the core Java team designed G1 as an improvement over CMS, patching some of its weaknesses with additional strategies.

G1使用多个后台GC线程来扫描和清除堆,就像CMS一样。实际上,Java核心团队将G1设计成对CMS的改进,用额外的策略修补了CMS的一些弱点。

In addition to the incremental and concurrent collection, it tracks previous application behavior and GC pauses to achieve predictability. It then focuses on reclaiming space in the most efficient areas first — those mostly filled with garbage. We call it Garbage-First for this reason.

除了增量和并发收集外,它跟踪以前的应用程序行为和GC暂停,以实现可预测性。然后,它专注于首先在最有效的区域回收空间–那些大多被垃圾填满的区域。出于这个原因,我们称其为垃圾优先

Since Java 9, G1 is the default collector for server-class machines. We can explicitly enable it by providing -XX:+UseG1GC on the command line.

自Java 9以来,G1是服务器级机器的默认采集器。我们可以通过在命令行上提供-XX:+UseG1GC来明确启用它。

Pros:

优点。

  • It’s very efficient with gigantic datasets.
  • It takes full advantage of multiprocessor machines.
  • It’s the most efficient in achieving pause time goals.

Cons:

弊端。

  • It’s not the best when there are strict throughput goals.
  • It requires the application to share resources with GC during concurrent collections.

G1 works best for applications with very strict pause-time goals and a modest overall throughput, such as real-time applications like trading platforms or interactive graphics programs.

G1最适合于具有非常严格的暂停时间目标和适度的总体吞吐量的应用,如交易平台或互动图形程序等实时应用。

4.5. Z Garbage Collector (ZGC)

4.5.Z垃圾收集器(ZGC)

ZGC is a scalable low latency garbage collector. It manages to keep low pause times on even multi-terabyte heaps. It uses techniques including reference coloring, relocation, load barriers and remapping. It is a good fit for server applications, where large heaps are common and fast application response times are required.

ZGC是一个可扩展的低延迟垃圾收集器。它设法在多TB的堆上保持低的暂停时间。它使用的技术包括参考着色、重定位、负载屏障和重映射。它很适合于服务器应用,在这些应用中,大堆是常见的,并且需要快速的应用响应时间。

It was introduced in Java 11 as an experimental GC implementation. We can explicitly enable it by providing -XX:+UnlockExperimentalVMOptions -XX:+UseZGC on the command line. For more detailed descriptions, please visit our article on Z Garbage Collector.

它是在Java 11中引入的,作为一种实验性的GC实现。我们可以通过在命令行上提供-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来明确启用它。关于更详细的描述,请访问我们关于Z垃圾收集器的文章

5. Conclusion

5.结论

For many applications, the choice of the collector is never an issue, as the JVM default usually suffices. That means the application can perform well in the presence of garbage collection with pauses of acceptable frequency and duration. However, this isn’t the case for a large class of applications, especially those with humongous datasets, many threads, and high transaction rates.

对于许多应用程序来说,收集器的选择从来不是一个问题,因为JVM默认值通常就足够了。这意味着应用程序可以在有垃圾收集的情况下,以可接受的频率和持续时间暂停,表现良好。然而,对于一大类应用程序来说,情况并非如此,特别是那些具有巨大数据集、许多线程和高交易率的应用程序。

In this article, we’ve explored the garbage collectors supported by the JVM. We’ve also looked at key variables that can help us choose the right collector for the needs of our application.

在这篇文章中,我们探讨了JVM所支持的垃圾收集器。我们还看了一些关键的变量,这些变量可以帮助我们为我们的应用程序的需求选择正确的收集器。