1. Introduction
1.绪论
In this tutorial, we’ll cover the basic problems with Java memory management and the need to constantly find better ways to achieve it. This will primarily cover the new experimental garbage collector introduced in Java called Shenandoah and how it compares against other garbage collectors.
在本教程中,我们将介绍Java内存管理的基本问题,以及不断寻找更好的方法来实现它的必要性。这将主要涵盖Java中引入的名为Shenandoah的新实验性垃圾收集器,以及它与其他垃圾收集器的比较。
2. Understanding Challenges in Garbage Collection
2.了解垃圾收集的挑战
A garbage collector is a form of automatic memory management where a runtime like JVM manages allocation and reclamation of memory for the user programs running on it. There are several algorithms to implement a garbage collector. These include reference counting, mark-sweep, mark-compact, and copying.
垃圾收集器是一种自动内存管理的形式,像JVM这样的运行时间为在其上运行的用户程序管理内存的分配和回收。有几种算法可以实现垃圾收集器。这些算法包括引用计数、标记扫除、标记压缩和复制。
2.1. Considerations for a Garbage Collector
2.1.垃圾收集器的考虑因素
Depending upon the algorithm we use for garbage collection, it can either run while the user program is suspended or run concurrently with the user program. The former achieves higher throughput at the cost of high latency due to long pauses, also known as stop-the-world pauses. The latter aims for better latency but compromises on throughput.
根据我们用于垃圾收集的算法,它可以在用户程序暂停时运行或与用户程序同时运行。前者实现了更高的吞吐量,但由于长时间的停顿,也就是所谓的 “停止-世界 “的停顿,导致了高延迟。后者旨在实现更好的延迟,但在吞吐量上有所妥协。
In fact, most modern-day collectors use a hybrid strategy, where they apply both stop-the-world and concurrent approaches. It usually works by dividing the heap space into young and old generations. Generational collectors then use the stop-the-world collection in the young generation and concurrent collection in the old generation, possibly in increments to reduce pauses.
事实上,大多数现代收藏家使用的是混合策略,他们同时应用停止-世界和并发的方法。它通常通过将堆空间分为年轻一代和老一代来工作。然后,各代收集器在年轻一代中使用停止-世界收集,在老一代中使用并发收集,可能以递增的方式来减少停顿。
Nevertheless, the sweet spot really is to find a garbage collector that runs with minimal pauses and provides high throughput — all this with a predictable behavior on heap size that can vary from small to very large! This is a constant struggle that has kept the pace of innovation in the Java garbage collection alive since the early days.
尽管如此,真正的亮点是找到一个运行时停顿最少、提供高吞吐量的垃圾收集器–所有这些都是对堆大小可预测的行为,而堆大小可以从小到大。这是一场持续的斗争,从早期开始就一直保持着Java垃圾收集的创新步伐。
2.2. Existing Garbage Collectors in Java
2.2.Java中现有的垃圾收集器
Some of the traditional garbage collectors include serial and parallel collectors. They are generational collectors and use copying in the young and mark-compact in the old generation:
一些传统的垃圾收集器包括串行和并行收集器。它们是代际收集器,在年轻一代使用复制,在老一代使用标记紧凑。
While providing good throughput, they suffer from the problem of long stop-the-world pauses.
虽然提供了良好的吞吐量,但它们存在着长时间停顿的问题。
The Concurrent Mark Sweep (CMS) collector introduced in Java 1.4 is a generational, concurrent, low-pause collector. It works with copying in the young generation and mark-sweep in the old generation:
Java 1.4中引入的并发标记扫描(CMS)收集器是一个分代的、并发的、低暂停的收集器。它在年轻一代中使用复制,在老一代中使用标记扫描。
It tries to minimize the pause time by doing most of the work concurrently with the user program. Nevertheless, it still has problems leading to unpredictable pauses, requires more CPU time, and is not suitable for a heap larger than 4 GB in size.
它试图通过与用户程序同时进行大部分工作来减少暂停时间。然而,它仍然存在导致不可预测的暂停的问题,需要更多的CPU时间,并且不适合于超过4GB大小的堆。
As a long-term replacement for CMS, the Garbage First (G1) collector was introduced in Java 7. G1 is a generational, parallel, concurrent, and incrementally compacting low-pause collector. It works with copying in the young generation and mark-compact in the old generation:
作为 CMS 的长期替代品,Garbage First (G1) 收集器在 Java 7 中被引入。G1是一个分代的、并行的、并发的、增量的、低暂停的收集器。它在年轻一代中使用复制,在老一代中使用标记压缩。
G1, however, is also a regionalized collector and structures the heap area into smaller regions. This gives it the benefit of more predictable pauses. Targeted for multiprocessor machines with a large amount of memory, G1 is also not free from the pauses.
然而,G1也是一个区域化的收集器,它将堆的区域结构成较小的区域。这给它带来了更可预测的暂停的好处。G1的目标是拥有大量内存的多处理器机器,也不能免于暂停。
So, the race to find a better garbage collector continues, especially one that reduces the pause time further. There’s a series of experimental collectors that JVM has introduced lately, like Z, Epsilon, and Shenandoah. Apart from that, G1 continues to get more improvements.
所以,寻找更好的垃圾收集器的竞赛仍在继续,尤其是能进一步减少暂停时间的垃圾收集器。最近JVM推出了一系列的实验性收集器,比如Z、Epsilon和Shenandoah。除此以外,G1继续得到更多的改进。
The objective is really to get as close as possible to a pauseless Java!
我们的目标其实是尽可能地接近无停顿的Java!
3. Shenandoah Garbage Collector
3.神农架垃圾收集器
Shenandoah is an experimental collector that has been introduced in Java 12 and is being positioned as a latency specialist. It tries to reduce pause times by doing more of its garbage collection work concurrently with the user program.
Shenandoah是在Java 12中引入的一个实验性收集器,被定位为延迟专家。它试图通过与用户程序同时进行更多的垃圾收集工作来减少暂停时间。
For instance, Shenendoah attempts to perform object relocation and compaction concurrently. This essentially means that the pause time in Shenandoah is no longer directly proportional to the heap size. Hence, it can provide consistent low-pause behavior, irrespective of the heap size.
例如,Shenendoah试图同时执行对象重定位和压缩。这实质上意味着Shenandoah的暂停时间不再与堆的大小成正比。因此,它可以提供一致的低暂停行为,无论堆的大小如何。
3.1. Heap Structure
3.1.堆栈结构
Shenandoah, like G1, is a regionalized collector. This means that it divides the heap area into a collection of equal-sized regions. A region is basically a unit of memory allocation or reclamation:
Shenandoah,像G1一样,是一个区域化的收集器。这意味着它将堆区域划分为同等大小的区域集合。一个区域基本上是一个内存分配或回收的单位。
But, unlike G1 and other generational collectors, Shenandoah doesn’t divide the heap area into generations. Therefore, it has to mark most of the live objects every cycle, which generational collectors can avoid.
但是,与G1和其他代际收集器不同的是,Shenandoah并没有将堆区分为几代。因此,它必须在每个周期标记大部分的活体,而代际收集器可以避免这一点。
3.2. Object Layout
3.2 对象布局
In Java, objects in memory don’t only include data fields — they carry some extra information as well. This extra information consists of the header, which contains a pointer to the object’s class, and the mark word. There are several uses for the mark word, like forwarding pointers, age bits, locking, and hashing:
在Java中,内存中的对象不仅包括数据字段,它们还携带一些额外的信息。这些额外的信息包括头,它包含一个指向对象类的指针,以及标记词。标记字有几种用途,如转发指针、年龄位、锁定和散列等。
Shenandoah adds an additional word to this object layout. This serves as the indirection pointer and allows Shenandoah to move objects without updating all of the references to them. This is also known as the Brooks pointer.
Shenandoah 为该对象布局添加一个额外的字。这作为指示指针,允许Shenandoah移动对象而不更新所有的引用。这也是也被称为布鲁克斯指针。
3.3. Barriers
3.3.障碍物
Performing a collection cycle in the stop-the-world mode is simpler, but the complexity just shoots up when we do that concurrently with the user program. It presents different challenges to the collection phases like concurrent marking and compaction.
在stop-the-world模式下执行一个收集周期是比较简单的,但是当我们与用户程序同时进行时,复杂性就会激增。它对收集阶段提出了不同的挑战,如并发标记和压实。
The solution lies in intercepting all heap accesses through what we call barriers. Shenandoah and other concurrent collectors like G1 make use of barriers to ensure heap consistency. However, barriers are costly operations and generally tend to reduce the throughput of a collector.
解决方案在于通过我们所说的屏障拦截所有的堆访问。Shenandoah和其他并发收集器,如G1,利用障碍物来确保堆的一致性。然而,障碍物是昂贵的操作,通常会降低收集器的吞吐量。
For instance, the read and write operations to an object may be intercepted by the collector using barriers:
例如,对一个对象的读写操作可以由收集器使用障碍物进行拦截。
Shenandoah makes use of multiple barriers in different phases, like the SATB barrier, read barrier, and write barrier. We’ll see where these are used in later sections.
Shenandoah 在不同的阶段使用了多个障碍,如SATB障碍、读障碍和写障碍。我们将在后面的章节中看到这些的使用情况。
3.4. Modes, Heuristics, and Failure Modes
3.4.模式、启发式方法和故障模式
Modes define the way Shenandoah runs, like which barriers it uses, and they also define its performance characteristics. There are three modes available: normal/SATB, iu, and passive. The normal/SATB mode is the default.
模式定义了Shenandoah的运行方式,比如它使用哪些障碍物,它们也定义了它的性能特征。有三种模式可供选择:正常/SATB、iu和被动。正常/SATB模式是默认的。
Heuristics determine when a collection should start and which regions it should include. These include adaptive, static, compact, and aggressive, with adaptive as the default heuristic. For instance, it may choose to select regions with 60 percent or more garbage and start a collection cycle when 75 percent of regions have been allocated.
启发式方法决定了一个集合应该何时开始,以及它应该包括哪些区域。这包括自适应、静态、紧凑和积极,其中自适应是默认的启发式方法。例如,它可以选择有60%或更多垃圾的区域,并在75%的区域被分配后开始一个收集周期。
Shenandoah needs to collect heap faster than the user program that allocates it. But, at times, it may fall behind, leading to one of the failure modes. These failure modes include pacing, degenerated collection, and in the worst case, a full collection.
Shenandoah需要比分配堆的用户程序更快地收集堆。但是,有时,它可能会落后,导致其中一种故障模式。这些故障模式包括步调一致、退化的收集,以及在最坏的情况下,完全收集。
4. Shenandoah Collection Phases
4.神农架收集阶段
Shenandoah’s collection cycle consists primarily of three phases: mark, evacuate, and update references. Although most of the work in these phases happens concurrently with the user program, there are still small parts that must happen in a stop-the-world mode.
Shenandoah的收集周期主要由三个阶段组成:标记、疏散和更新引用。尽管这些阶段的大部分工作都是与用户程序同时进行的,但仍有一些小部分必须在停止-世界模式下进行。
4.1. Marking
4.1.标识
Marking is the process of identifying all objects in the heap or parts of it that are unreachable. We can do this by starting from the root objects and traversing the object graph to find reachable objects. While traversing, we also assign each object one of three colors: white, grey, or black:
标记是识别堆中的所有对象或部分对象是不可到达的过程。我们可以通过从根对象开始,遍历对象图来寻找可到达的对象来做到这一点。在遍历过程中,我们还为每个对象分配三种颜色之一:白色、灰色或黑色。
Marking in the stop-the-world mode is simpler, but it gets complicated in concurrent mode. This is because the user program concurrently mutates the object graph while marking is in progress. Shenandoah solves this by using the Snapshot At the Beginning (SATB) algorithm.
在stop-the-world模式下打标比较简单,但在concurrent模式下会变得复杂。这是因为在标记过程中,用户程序会并发地突变对象图。Shenandoah通过使用Snapshot At the Beginning(SATB)算法来解决这个问题。
This means that any object that was alive at the beginning of the marking or that has been allocated since the beginning of marking is considered live. Shenandoah makes use of the SATB barrier to maintain the SATB view of the heap.
这意味着任何在标记开始时是活的对象,或者在标记开始后被分配的对象都被认为是活的。Shenandoah 利用SATB屏障来维护堆的SATB视图。
While most of the marking is done concurrently, there are still some parts that are done in stop-the-world mode. The parts that happen in the stop-the-world mode are the init-mark to scan the root set and the final-mark to drain all pending queues and re-scan the root set. The final-mark also prepares the collection set that indicates the regions to be evacuated.
虽然大多数标记是同时进行的,但仍有一些部分是在停止-世界模式下进行的。在 “停止-世界 “模式下发生的部分是扫描根集的初始标记和耗尽所有待定队列并重新扫描根集的最终标记。最后标记还准备了表示要撤离的区域的集合。
4.2. Cleanup and Evacuation
4.2.清理和疏散
Once the marking is complete, the garbage regions are ready to be reclaimed. The garbage regions are the regions where no live objects are present. The cleanup happens concurrently.
一旦标记完成,垃圾区域就可以被回收了。垃圾区域是没有活对象存在的区域。清理工作同时发生。
Now, the next step is to move the live objects in the collection set to other regions. This is done to reduce the fragmentation in memory allocation and, hence, is also known as compact. Evacuation or compacting happens entirely concurrently.
现在,下一步是将集合中的活对象转移到其他区域。这样做是为了减少内存分配中的碎片,因此,也被称为压缩。疏散或压缩完全是同时发生的。
Now, this is where Shenandoah is different from other collectors. A concurrent relocation of objects is tricky as the user program continues to read and write them. Shenandoah manages to achieve this by performing a compare-and-swap operation on the Brooks pointer of an object to point to its to-space version:
现在,这就是Shenandoah与其他采集器的不同之处。由于用户程序继续读取和写入对象,对象的并发重定位是很棘手的。Shenandoah通过对对象的Brooks指针进行比较和交换操作来实现这一目标,以指向其到空间的版本。
Further, Shenandoah uses the read and write barriers to ensure that a strict “to-space” invariant is maintained during the concurrent evacuation. What this means is that the read and write must happen from the to-space that is guaranteed to survive the evacuation.
此外,Shenandoah使用读写障碍来确保在同时进行的疏散过程中保持严格的 “到空间 “不变性。这意味着读和写必须从保证在疏散过程中存活的to-space发生。
4.3. Reference Update
4.3.参考资料更新
This phase in the collection cycle is to traverse through the heap and update the references to objects that were moved during the evacuation:
采集周期的这一阶段是遍历堆并更新在疏散过程中被移动的对象的引用。
The update reference phase is, again, mostly done concurrently. There are brief periods of init-update-refs that initialize the update reference phase and final-update-refs that re-update the root set and recycle the regions from the collection set. Only these require the stop-the-world mode.
更新引用阶段也是主要是同时进行的。有短暂的init-update-refs时期,用于初始化更新参考阶段,还有final-update-refs时期,用于重新更新根集和回收集合中的区域。只有这些需要停止-世界模式。
5. Comparision With Other Experimental Collectors
5.与其他实验性收集器的比较
Shenandoah is not the only experimental garbage collector that has been introduced recently in Java. Others include Z and Epsilon. Let’s understand how they compare against Shenandoah.
Shenandoah并不是最近在Java中引入的唯一实验性垃圾收集器。其他的包括Z和Epsilon。让我们了解一下它们与Shenandoah的比较。
5.1. Z Collector
5.1.Z采集器
Introduced in Java 11, the Z collector is a single-generation, low-latency collector designed for very large heap sizes — we’re talking multi-terabyte territory. The Z collector does most of its work concurrently with the user program and leverages the load barrier for heap references.
Z 采集器是单代、低延迟的采集器,专为非常大的堆尺寸而设计 – 我们所说的是多TB的领域。Z收集器的大部分工作都是与用户程序同时进行的,并且利用了堆引用的负载屏障。
Further, the Z collector takes advantage of 64-bit pointers with a technique called pointer coloring. Here, the colored pointers store extra information about objects on the heap. The Z collector remaps objects using the extra information stored in the pointer to reduce memory fragmentation.
此外,Z收集器利用了64位指针的优势,采用了一种叫做指针着色的技术。在这里,着色的指针存储了关于堆上对象的额外信息。Z收集器使用存储在指针中的额外信息重新映射对象,以减少内存碎片。
Broadly speaking, the Z collector’s goals are similar to those of Shenandoah. They both aim to achieve low pause times that are not directly proportional to the heap size. However, there are more tuning options available with Shenandoah than with the Z collector.
大体上,Z收集器的目标与Shenandoah的目标相似。它们的目标都是实现低暂停时间,而暂停时间与堆的大小不成正比。然而,Shenandoah比Z收集器有更多的调整选项。
5.2. Epsilon Collector
5.2.Epsilon收集器
Epsilon, also introduced in Java 11, has a very different approach to garbage collection. It’s basically a passive or “no-op” collector, which means that it handles memory allocation but doesn’t recycle it! So, when the heap runs out of memory, the JVM simply shuts down.
Epsilon,也是在 Java 11 中引入的,对垃圾收集有非常不同的方法。它基本上是一个被动的或 “无操作 “的收集器,这意味着它处理内存分配,但不回收内存!因此,当堆耗尽时,它就会停止工作。因此,当堆的内存耗尽时,JVM会简单地关闭。
But why would we ever want to use a collector like that? Basically, any garbage collector has an indirect impact on the performance of the user program. It’s very difficult to benchmark an application and understand the impact of garbage collection on it.
但是我们为什么要使用这样的收集器呢?基本上,任何垃圾收集器都会对用户程序的性能产生间接影响。对一个程序进行基准测试并了解垃圾收集对它的影响是非常困难的。
Epsilon serves exactly that purpose. It simply removes the impact of a garbage collector and lets us run the application in isolation. But, this expects us to have a very clear understanding of the memory requirements of our application. Consequently, we can achieve better performance from the application.
Epsilon正是为这个目的服务的。它只是消除了垃圾收集器的影响,让我们在隔离状态下运行应用程序。但是,这期望我们对我们的应用程序的内存需求有一个非常清楚的了解。因此,我们可以从应用程序中获得更好的性能。
Clearly, Epsilon has a very different goal from that of Shenandoah.
显然,Epsilon的目标与Shenandoah的目标非常不同。
6. Conclusion
6.结语
In this article, we went through the basics of garbage collection in Java and the need to constantly improve it. We discussed in detail the most recent experimental collector introduced in Java — Shenandoah. We also went through how it fares against the other experimental collectors available in Java.
在这篇文章中,我们回顾了Java中垃圾收集的基本原理,以及不断改进垃圾收集的必要性。我们详细讨论了Java中最新推出的实验性收集器–Shenandoah。我们还讨论了它与Java中其他实验性收集器的对比情况。
The pursuit of a universal garbage collector isn’t going to be realized anytime soon! So, while G1 remains the default collector, these new additions provide us with options to use Java in low-latency situations. However, we shouldn’t consider them as a drop-ship replacement of other high-throughput collectors.
追求一个通用的垃圾收集器是不会很快实现的!因此,虽然G1仍然是默认的收集器,但这些新增加的功能为我们提供了在低延迟情况下使用Java的选择。然而,我们不应该把它们看作是其他高吞吐量收集器的落地替代。