1. 概述
在这篇简短的文章中,我们将了解JVM如何确保收集无法访问但循环的引用。
首先,我们将探索不同类型的GC算法。之后,我们将了解JVM中如何处理循环引用。
还值得一提的是,GC不是JVM规范的一部分,由实现者自行决定。因此,每个JVM实现可能有不同的GC策略或根本没有。
在本文中,我们关注一种特定的JVM实现:HotSpot JVM。在整篇文章中,我们也可能交替使用JVM和HotSpot JVM这两个术语。
2. 引用计数
引用计数GC算法将引用计数与每个对象相关联,只要对该对象的引用数大于0,这些算法就会认为该对象处于活跃状态。通常,运行时将引用计数存储在对象头中。
在一个非常简单的实现中,每个对对象的新引用都应该触发一个原子引用计数增量。同样,每个新的取消引用都应该触发一个原子递减。
Swift编程语言使用一种引用计数形式进行内存管理。另外,JVM中没有基于引用计数的GC算法。
2.1 优点和缺点
从好的方面来说,引用计数可以在整个应用程序生命周期中分散内存管理成本,因为(几乎)没有周期性的GC问题。此外,它可能会在对象的引用计数达到0并变成垃圾时立即销毁对象。
引用计数也不是免费的午餐,在简单的实现中,更新引用计数可能效率低下,因为我们需要以原子方式递增或递减它。在这方面,很少有优化可以使引用计数更有效,例如延迟或缓冲引用计数方法。
但是,引用计数仍然存在一个严重的问题:它无法回收循环引用。
例如,假设对象A引用对象B,反之亦然。即使A和B从对象图中的其余部分变得不可访问,它们的引用计数也永远不会达到0,这是因为它们仍然相互持有引用。
事实证明,这类循环引用在计算机科学中非常普遍。例如,让我们考虑以下双向链表。首先,另一个对象引用了该列表:
链表可以从对象D到达,因此不应收集它,并且引用计数与此预期一致。现在,假设对象D本身变得不可访问:
尽管链表现在也不可达,但其组件的引用计数不止1。因此,使用这种简单的引用计数实现,运行时不会将此链表视为垃圾,即使它是。
3. 跟踪GC
跟踪收集器将通过从一组根对象(称为GC根)跟踪它们来确定对象的可达性,如果一个对象可以从根对象直接或间接到达,那么它将被认为是存活的。其他对象则无法访问,并将成为收集的候选对象:
一个简单的跟踪收集器的工作方式如下。从GC根开始,它递归地遍历对象图,直到没有更多的灰色对象可供访问。最后,它认为所有的白色对象都是不可访问的并且是收集的候选对象。这是三色标记算法的简单描述。
我们可以将GC根视为我们确定还活着的对象,例如,这些是Java和JVM中的一些GC根:
- 局部变量或栈帧现在引用的任何内容,这些变量被当前正在执行的方法使用,所以我们不想收集它们
- 实时线程
- 静态变量
- 系统类加载器加载的类
- JNI局部变量和全局变量
与引用计数收集器不同,跟踪收集器会定期执行收集过程。因此,在大多数情况下,分配和取消分配应该很快。但是,当GC开始时,可能会出现一些问题。
从好的方面来说,这些GC算法不会受到循环引用的影响。他们不是计算对每个对象的引用,而是从GC根开始遍历对象图。因此,即使存在一些循环引用,只要对象不可达,也会被回收,如上图所示。
非常有趣的是,将备份跟踪收集器与引用计数GC结合使用是修复引用计数中的循环引用的传统方法之一。
3.1 HotSpot JVM
在撰写本文时,HotSpot JVM中的所有GC实现都是跟踪收集器,包括CMS、G1和ZGC。因此,JVM不会遇到循环引用问题,这是本文的关键要点。
4. 总结
在这篇简短的文章中,我们了解了JVM如何处理循环引用。
要了解有关垃圾回收的更详细处理,强烈建议查看垃圾回收手册。
Post Directory
