1. Overview
1.概述
In this quick article, we’ll see how the JVM makes sure to collect the unreachable but cyclic references.
在这篇快速文章中,我们将看到JVM是如何确保收集那些无法到达但循环的引用。
First, we’ll explore different types of GC algorithms. After that, we’re going to see how the cyclic references are handled in the JVM.
首先,我们将探索不同类型的GC算法。之后,我们要看看JVM中是如何处理循环引用的。
It’s also worth mentioning that GC is not part of the JVM specification and is left to the discretion of the implementor. Therefore, each JVM implementation may have different GC strategies or none at all.
还值得一提的是,GC并不是JVM规范的一部分,而是留给实现者的决定权。因此,每个JVM实现都可能有不同的GC策略,或者根本就没有GC策略。
In this article, we’re focusing on one specific JVM implementation: the HotSpot JVM. We also may use the JVM and HotSpot JVM terms interchangeably throughout the article.
在本文中,我们专注于一个特定的JVM实现:HotSpot JVM。我们也可能在文章中交替使用JVM和HotSpot JVM的术语。
2. Reference Counting
2.参考计数
Reference counting GC algorithms associate a reference count with each object. These algorithms consider an object to be alive as long as the number of references to that object is greater than zero. Usually, the runtime stores the reference count in the object header.
引用计数GC算法将引用计数与每个对象相关联。这些算法认为,只要对一个对象的引用数量大于零,该对象就是活的。通常情况下,运行时将引用计数存储在对象头中。
In a very naive implementation, each new reference to an object should trigger an atomic reference count increment. Likewise, each new dereference should trigger an atomic decrement.
在一个非常天真的实现中,对一个对象的每个新引用都应该触发一个原子引用计数的增加。同样地,每一个新的解除引用都应该触发一个原子递减。
The Swift programming language uses a form of reference counting for memory management. Also, there is no GC algorithm based on reference counting in the JVM.
Swift编程语言使用一种引用计数的形式进行内存管理。另外,JVM中没有基于引用计数的GC算法。
2.1. Pros and Cons
2.1.优点和缺点
On the bright side, reference counting can distribute memory management costs throughout the application lifecycle, as there are (almost) no periodic GC hiccups. Also, it can potentially destroy the objects as soon as their reference count reaches zero and become garbage.
从好的方面看,引用计数可以在整个应用生命周期内分配内存管理成本,因为(几乎)没有周期性的GC打嗝。另外,一旦对象的引用计数达到零,它就有可能销毁这些对象,成为垃圾。
Reference counting is no free lunch, either. In the naive implementation, updating the reference count can be inefficient as we need to increment or decrement it atomically. Few optimizations can make reference counting more efficient in this regard, such as deferred or buffered reference counting approaches.
引用计数也不是免费的午餐。在天真的实现中,更新引用计数的效率可能很低,因为我们需要原子式地增加或减少它。很少有优化可以使引用计数在这方面更有效率,比如延迟或缓冲引用计数方法。
However, there is still one serious issue with reference counting: it can’t reclaim cyclic references.
然而,引用计数仍有一个严重的问题:它不能回收循环引用。
For instance, suppose object A refers to object B and vice versa. Even if A and B become unreachable from the rest of the object graph, their reference count will never reach zero. That’s because they still hold a reference to each other.
例如,假设对象A引用对象B,反之亦然。即使A和B变得无法从对象图的其他部分到达,它们的引用计数也不会达到零。这是因为它们仍然持有彼此的引用。
As it turns out, these sorts of cyclic references are pretty common in computer science. For example, let’s consider the following doubly-linked list. At first, another object has a reference to the list:
事实证明,这类循环引用在计算机科学中相当常见。例如,让我们考虑下面这个双链的列表。起初,另一个对象对该列表有一个引用。
The linked list is reachable from the object D, so it shouldn’t be collected, and the reference counts are aligned with this expectation. Now, suppose the object D itself becomes unreachable:
链表可以从对象D到达,所以它不应该被收集,并且引用计数与这个期望一致。现在,假设对象D本身变得不可达。
Even though the linked list is also unreachable now, the reference count for its components is more than one. Therefore, with this naive reference counting implementation, the runtime won’t consider this linked list as garbage, even though it is.
尽管这个链表现在也是不可达的,但是它的组件的引用计数是超过1的。因此,通过这种天真的引用计数实现,运行时不会将这个链表视为垃圾,即使它是垃圾。
3. Tracing GCs
3.追踪GCs
Tracing collectors will determine the objects’ reachability by tracing them from a set of root objects, known as GC roots. If an object is reachable from a root object, either directly or indirectly, then it will be considered alive. Others are unreachable and candidates for collection:
追踪收集器将通过从一组根对象(被称为GC根)追踪来确定对象的可达性。如果一个对象可以直接或间接地从根对象中到达,那么它将被认为是活着的。其他对象则是无法到达的,是被收集的候选对象。
Here’s how a simple tracing collector works. Starting from the GC roots, it traverses the object graph recursively until there are no more gray objects left to visit. In the end, it considers all the white objects unreachable and candidates for collection. This is a simple depiction of the tri-color marking algorithm.
下面是一个简单的跟踪收集器的工作方式。从GC根部开始,它递归地遍历对象图,直到没有更多的灰色对象可以访问。最后,它认为所有的白色对象都无法到达,是收集的候选对象。这是三色标记算法的一个简单描述。
We can think of GC roots as objects that we’re sure are alive. For instance, these are some GC roots in Java and JVM:
我们可以把GC根想象成我们确信是活的对象。例如,这些是Java和JVM中的一些GC根。
- Local variables or anything stack frames are referring to right now. These variables are used by currently executing methods, so we don’t want to collect them
- Live threads
- Static variables
- Classes loaded by the system classloader
- JNI locals and globals
Tracing collectors, as opposed to reference counting collectors, will perform the collection process periodically. So, for most of the time, allocations and assignments should work fast. However, when the GC kicks off, there might be some hiccups.
追踪收集器,相对于引用计数收集器,将定期执行收集过程。因此,在大部分时间里,分配和赋值应该工作得很快。然而,当GC启动的时候,可能会有一些小插曲。
On the bright side, these GC algorithms won’t suffer from cyclic references. Instead of counting the references to each object, they traverse the object graph starting from the GC roots. Therefore, even if there are some cyclic references, objects will be collected as long as they’re unreachable, as shown in the above diagram.
好的一面是,这些GC算法不会受到循环引用的影响。它们不是计算每个对象的引用,而是从GC根开始遍历对象图。因此,即使存在一些循环引用,只要对象无法到达,就会被收集,如上图所示。
Quite interestingly, using a backup tracing collector in tandem with a reference counting GC is one of the conventional approaches to fix the cyclic references in reference counting.
颇为有趣的是,将备份跟踪收集器与参考计数GC串联使用,是修复参考计数中循环参考的传统方法之一。
3.1. The HotSpot JVM
3.1.HotSpot JVM
All GC implementations in the HotSpot JVM, as of this writing, are tracing collectors, including CMS, G1, and ZGC. So, the JVM won’t suffer from the cyclic reference issue. That’s the key takeaway from this article!
HotSpot JVM中的所有GC实现,截至本文编写时,都是跟踪收集器,包括CMS、G1和ZGC。所以,JVM不会受到循环引用问题的影响。这就是这篇文章的关键收获!
4. Conclusion
4.总结
In this quick article, we saw how the JVM handles cyclic references.
在这篇快速文章中,我们看到了JVM如何处理循环引用。
For a more detailed treatment of garbage collection, it’s highly recommended to check out the garbage collection handbook.
对于垃圾收集的更详细的处理,强烈建议你查看垃圾收集手册。