Python的垃圾回收机制是否处理这样的引用循环?

17

使用objgraph,我发现了很多这样的对象:

InstanceState loop

Python的垃圾回收器是否会处理这样的循环引用,还是会造成内存泄漏呢?

循环引用的更广阔视图:

Wider view of InstanceState loop


请参见:https://stackoverflow.com/q/15974561/1959808 - 0 _
4个回答

28

Python的标准引用计数机制无法释放循环引用对象,所以您示例中的结构将会泄漏。

然而,默认情况下启用了补充垃圾回收机制,如果该结构中的组件不再从外部可达并且没有__del__()方法,则应该能够释放它们 。

如果有__del__()方法,则垃圾回收器不会释放它们,因为它无法确定运行这些__del__()方法的安全顺序。


10
应明确指出,Python中称为“补充”的GC默认情况下是启用的。 - Eli Bendersky
3
太好了!至少有人(你和Raymond Hettinger)不会混淆垃圾回收和引用计数,并且不会将“垃圾收集器”用作“引用计数机制”的同义词!我一直感到困惑,因为我理解了这些概念,但我经常阅读到的文本表达的观点却让人们认为GC是负责清除无需再次引用的对象的罪魁祸首。在你的答案中,区别被完美地表达并且非常清晰。谢谢。因此+1 - eyquem
由于仍然可以通过搜索找到这里,注意此答案已不再正确。__del__不再防止循环引用的收集,并且对于循环引用确实会被调用。 - Glenn Maynard

22

稍微扩展一下Frédéric的回答,文档中的“引用计数”部分很好地解释了补充循环检测。

由于我发现解释事物是确认我理解的好方法,这里有一些例子... 假设有这两个类:

class WithDel(object):
    def __del__(self):
        print "deleting %s object at %s" % (self.__class__.__name__, id(self))


class NoDel(object):
    pass

创建一个对象并从a中失去引用,由于引用计数的原因会触发__del__方法:

>>> a = WithDel()
>>> a = None  # leaving the WithDel object with no references 
deleting WithDel object at 4299615184

如果我们在两个没有__del__方法的对象之间创建一个引用循环,这次由于循环检测,所有内容仍然是无泄漏的。首先,启用垃圾收集调试输出:
>>> import gc
>>> gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS)

然后在这两个对象之间创建一个引用循环:

>>> a = NoDel(); b = NoDel()
>>> a.other = b; b.other = a  # cyclical reference
>>> a = None; b = None # Leave only the reference-cycle
>>> gc.collect()
gc: collectable <NoDel 0x10046ed50>
gc: collectable <NoDel 0x10046ed90>
gc: collectable <dict 0x100376c20>
gc: collectable <dict 0x100376b00>
4
>>> gc.garbage
[]

(字典来自对象内部的__dict__属性)
一切都很好,直到循环中的任何一个对象包含__del__方法为止:
>>> a = NoDel(); b = WithDel()
>>> a.other = b; b.other = a
>>> a = None; b = None
>>> gc.collect()
gc: uncollectable <WithDel 0x10046edd0>
gc: uncollectable <dict 0x100376b00>
gc: uncollectable <NoDel 0x10046ed90>
gc: uncollectable <dict 0x100376c20>
4
>>> gc.garbage
[<__main__.WithDel object at 0x10046edd0>]

正如Paul所提到的,循环可以使用weakref打破:

>>> import weakref
>>> a = NoDel(); b = WithDel()
>>> a.other = weakref.ref(b)
>>> b.other = a # could also be a weakref

当对 WithDel 对象的引用 b 丢失时,即使存在循环引用,该对象也会被删除:

>>> b = None
deleting WithDel object at 4299656848
>>> a.other
<weakref at 0x10045b9f0; dead>

哦,objgraph会有帮助地像这样指出有问题的__del__方法


这在Python2中对我有效,但是在Python3中我无法重现泄漏...有什么改变了吗? - kralyk
2
@kralyk 我猜这个 PEP 负责:https://www.python.org/dev/peps/pep-0442/ - ead

5

Python的垃圾回收机制旨在遍历所有活动对象,以查找并消除没有外部引用的引用循环。

您可以通过运行gc.collect(),然后打印gc.garbagegc.get_objects来验证正在发生的情况。


2
如果您使用弱引用作为父指针,那么垃圾回收将正常进行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接