【问题标题】:Does Python GC deal with reference-cycles like this?Python GC 会处理这样的引用循环吗?
【发布时间】:2011-12-22 23:27:17
【问题描述】:

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

Python 的垃圾收集器会处理这样的循环,还是会泄漏?

循环的略宽视图:

【问题讨论】:

标签: python memory-leaks garbage-collection objgraph


【解决方案1】:

Python 的标准引用计数机制无法释放循环,因此示例中的结构会泄漏。

不过,supplemental garbage collection facility 默认是启用的,并且应该能够释放该结构,如果它的任何组件都无法从外部访问 并且它们没有 __del__() 方法 .

如果这样做,垃圾收集器will not free them 因为它无法确定运行这些__del__() 方法的安全顺序。

【讨论】:

  • 需要明确说明的是,你称之为“补充”的GC在Python中是默认开启的
  • 万岁!至少,人们(你和 Raymond Hettinger)不会混淆 GC 和 refs 计数器,也不会使用“垃圾收集器”作为“引用计数机制”的同义词!很长一段时间以来,我一直对自己理解这样的事情感到困惑,但我一直在阅读人们表达他们想法的文本,就好像 GC 负责杀死不再引用的对象一样。在您的回答中,差异得到了完美表达和明确。谢谢你。因此+1
  • 由于仍然可以在此处搜索您的方式,请注意此答案不再正确。 __del__ 不再阻止循环的收集,它会被调用循环。
【解决方案2】:

为了稍微扩展 Frédéric 的回答,文档的the "reference counts" section 很好地解释了补充循环检测。

因为我发现解释事情是确认我理解的好方法,这里有一些例子......使用这两个类:

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

然后当b 对WithDel 对象的引用丢失时,它会被删除,尽管循环:

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

哦,objgraph 会很有帮助的 indicated the problematic __del__ method like this

【讨论】:

  • 这在 python2 中对我有用,但是在 python3 中我无法重现泄漏...有什么改变吗?
  • @kralyk 我猜这个 pep 负责:python.org/dev/peps/pep-0442
【解决方案3】:

Python 的 GC 旨在遍历所有活动对象以定位和消除没有外部引用的引用循环。

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

【讨论】:

    【解决方案4】:

    如果你对父指针使用weakrefs,那么GC会正常发生。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-27
      • 2011-04-27
      • 1970-01-01
      • 2014-04-13
      • 1970-01-01
      相关资源
      最近更新 更多