Java垃圾收集如何与循环引用一起工作?
根据我的理解,Java中的垃圾回收会清除某些对象,如果没有其他东西“指向”该对象的话。
我的问题是,如果我们有这样的事情会发生什么事情:
class Node { public object value; public Node next; public Node(object o, Node n) { value = 0; next = n;} } //...some code { Node a = new Node("a", null), b = new Node("b", a), c = new Node("c", b); a.next = c; } //end of scope //...other code
a
, b
和c
应该被垃圾收集,但是它们都被其他对象引用。
Java垃圾回收如何处理这个问题? (或者它只是一个内存泄漏?)
Java的GC认为对象“垃圾”,如果它们无法通过垃圾收集根开始的链,那么这些对象将被收集。 即使物体可能指向彼此形成一个循环,但如果它们从根部切断,它们仍然是垃圾。
请参阅附录A:有关Java平台性能:策略和策略中垃圾回收的真相。
是的Java垃圾收集器处理循环参考!
How?
有一些称为垃圾收集根(GC根)的特殊对象。 这些都是可以访问的,任何拥有它们的根目录都是可以访问的。
一个简单的Java应用程序具有以下GC根源:
- 主要方法中的局部variables
- 主线程
- 主类的静态variables
为了确定哪些对象不再被使用,JVM间歇地运行一个非常合适的称为标记和扫描algorithm 。 它的工作原理如下
- 该algorithm遍历所有对象引用,从GC根开始,并标记每个find的对象。
- 没有被标记的对象占用的所有堆内存都被回收。 它被简单地标记为空闲的,基本上没有未使用的对象。
因此,如果任何对象不能从GC根到达(即使它是自引用的或循环引用的),它将经受垃圾收集。
当然,如果程序员忘记取消引用一个对象,这可能会导致内存泄漏。
来源: Java内存pipe理
垃圾收集器从一些总是被认为“可达”的“根”集合开始,比如CPU寄存器,堆栈和全局variables。 它通过在这些区域find任何指针,并recursion地find他们指向的所有东西。 一旦发现这一切 , 一切都是垃圾。
当然,还有一些变化,主要是为了速度。 例如,大多数现代垃圾收集器都是“世代”的,意思是把物体分成几代,随着物体变旧,垃圾收集器在时间间隔越来越长,试图弄清楚物体是否仍然有效 – 它只是开始假设,如果它活了很长时间,机会是相当好的,它会继续活得更久。
尽pipe如此,基本思想仍然是一样的:它们都是基于从一些根本理所当然的东西开始,然后追踪所有的指针,find还有什么可以使用的东西。
有意思的是:人们常常会对垃圾收集器的这部分和用于封送对象的代码(如远程过程调用)之间的相似程度感到惊讶。 在每种情况下,你都从一些根对象开始,然后追着指针find所有引用它们的对象。
你是对的。 你描述的垃圾收集的具体forms被称为“ 引用计数 ”。 在最简单的情况下,它的工作方式(概念上,至less大多数引用计数的现代实现实际上是以不同的方式实现的),看起来像这样:
- 每当添加一个对象的引用(例如,它被分配给一个variables或一个字段,传递给方法,等等),其引用计数增加1
- 每当一个对象的引用被移除(该方法返回,variables超出范围,该字段被重新分配给一个不同的对象或包含该字段的对象本身被垃圾回收),引用计数减less1
- 一旦引用计数达到0,就不再提及该对象,这意味着没有人可以使用它,因此它是垃圾,可以收集
而这个简单的策略恰恰就是你描述的问题:如果A引用B和B引用A,那么它们的引用计数都不能小于1,这意味着它们将永远不会被收集。
有四种方法可以解决这个问题:
- 忽略它。 如果你有足够的内存,你的周期很小,而且你的运行时间很短,也许你可以不用收集周期而逃脱。 想想一个shell脚本解释器:shell脚本通常只运行几秒钟,并不分配太多的内存。
- 将您的引用计数垃圾收集器与另一个垃圾收集器组合在一起,这个垃圾收集器没有周期问题 例如,CPython执行此操作:CPython中的主垃圾回收器是一个引用计数收集器,但不时会运行一个跟踪垃圾回收器来收集周期。
- 检测周期。 不幸的是,在图中检测周期是一个相当昂贵的操作。 特别是跟踪收集器需要几乎相同的开销,所以你可以使用其中的一个。
- 不要以你自己的方式来实现这个algorithm:自从20世纪70年代以来,已经有多个相当有趣的algorithm开发出来,它们在一次操作中以一种聪明的方式将循环检测和参考计数结合起来,无论是单独还是做collections追踪。
顺便说一句, 另一个实现垃圾收集器的主要方式(我已经暗示了几次以上)正在追查 。 跟踪收集器基于可达性的概念。 从一开始你知道总是可以访问的根集 (例如全局常量,或者Object
类,当前的词法范围,当前的栈帧),然后从那里跟踪所有可以从根集访问的对象,那么所有可以从根集到达的对象都可以访问的对象,等等,直到你有传递闭包。 所有不在封闭的东西都是垃圾。
由于一个循环只能在本身内部达到,但不能从根集访问,因此将被收集。
Java GC实际上并不像您所描述的那样。 更准确地说,他们从基本的一组对象开始,经常被称为“GC根”,并且会收集任何从根目录无法到达的对象。
GC的根源包括:
- 静态variables
- 当前在运行线程的堆栈中的局部variables(包括所有可用的“this”引用)
所以,就你的情况而言,一旦局部variablesa,b和c在方法结束时超出了范围,就不会有更多的GC根直接或间接地包含对三个节点中的任何一个的引用,他们将有资格进行垃圾回收。
豆腐啤酒的链接有更多的细节,如果你想要它。
这篇文章深入了解垃圾收集器(概念上…有几个实现)。 您的post的相关部分是“A.3.4无法访问”。
垃圾收集通常并不意味着“清理某个对象,如果没有其他东西”指向“该对象”(这是引用计数)。 垃圾收集大致意味着find程序无法访问的对象。
因此,在您的示例中,a,b和c超出范围后,可以由GC收集,因为您无法再访问这些对象。
比尔直接回答你的问题。 正如Amnon所说的,你对垃圾收集的定义只是引用计数。 我只是想补充一点,即使非常简单的algorithm,如标记和扫描和复制收集,很容易处理循环引用。 所以,没有什么魔力!