Python的垃圾回收会处理这种引用循环吗?

17 投票
4 回答
7791 浏览
提问于 2025-04-17 05:44

使用 objgraph,我发现了一堆像这样的对象:

InstanceState loop

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

这是循环的稍微宽一点的视图:

Wider view of InstanceState loop

4 个回答

5

Python的垃圾回收机制是用来检查所有活着的对象,找到那些没有外部引用的循环引用,并把它们清除掉。

你可以通过运行 gc.collect() 来验证这个过程,然后打印出 gc.garbagegc.get_objects 来查看结果。

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来自对象内部的__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__方法,如下所示

28

Python的标准引用计数机制无法处理循环引用,所以你提到的结构会导致内存泄漏。

不过,Python默认开启了一个额外的垃圾回收机制,它可以释放这些结构,只要这些结构的组成部分不再被外部访问到,并且它们没有定义__del__()方法。

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

撰写回答