返回介绍

建议69:对象的管理与垃圾回收

发布于 2024-01-30 22:19:09 字数 2738 浏览 0 评论 0 收藏 0

通常来说Python并不需要用户自己来管理内存,它与Perl、Ruby等很多动态语言一样具备垃圾回收功能,可以自动管理内存的分配与回收,而不需要编程人员的介入。那么这样是不是意味着用户可以高枕无忧了呢?我们来看下面一个例子:

class Leak(object):
     def __init__(self):
         print "object with id %d was born" %id(self)
while(True):
     A = Leak()
     B = Leak()
     A.b = B
     B.a = A
     A = None
     B = None

运行上述程序我们会发现,Python进程的内存消耗量一直在持续增长,到最后出现内存耗光的情况。这是什么原因造成的呢?

我们先来简单谈谈Python中内存管理的方式:Python使用引用计数器(Reference counting)的方法来管理内存中的对象,即针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。当其他对象引用该对象时,其引用计数会增加1,而删除一个对当前对象的引用,其引用计数会减1。只有当引用计数的值为0的时候该对象才会被垃圾收集器回收,因为它表示这个对象不再被其他对象引用,是个不可达对象。引用计数算法最明显的缺点是无法解决循环引用的问题,即两个对象相互引用。上述代码中正是由于形成了A、B对象之间的循环引用而造成了内存泄露的情况,因为两个对象的引用计数器都不为0,该对象并不会被垃圾收集器回收,而无限循环导致一直在申请内存而没有释放,所以最后出现了内存耗光的情况。

循环引用常常会在列表、元组、字典、实例以及函数使用时出现。对于由循环引用而导致的内存泄露的情况,有没有办法进行控制和管理呢?实际上Python自带了一个gc模块,它可以用来跟踪对象的“入引用(incoming reference)”和“出引用(outgoing reference)”,并找出复杂数据结构之间的循环引用,同时回收内存垃圾。有两种方式可以触发垃圾回收:一种是通过显式地调用gc.collect()进行垃圾回收;还有一种是在创建新的对象为其分配内存的时候,检查threshold阈值,当对象的数量超过threshold的时候便自动进行垃圾回收。默认情况下阈值设为(700,10,10),并且gc的自动回收功能是开启的,这些可以通过gc.isenabled()查看。下面是gc模块使用的简单例子:

>>> import gc
>>> print gc.isenabled()
True
>>> gc.isenabled()
True
>>> gc.get_threshold()
(700, 10, 10)

对于本节开头的例子,我们使用gc模块来进行垃圾回收,代码如下:

def main():
  collected = gc.collect()
  print "Garbage collector before running: collected %d objects." % (collected)
  print "Creating reference cycles..."
  A = Leak()
  B = Leak()
  A.b = B
  B.a = A
  A = None
  B = None
  collected = gc.collect()
  pprint.pprint( gc.garbage)
  print "Garbage collector after running: collected %d objects." % (collected)
if __name__ == "__main__":
  ret = main()
  sys.exit(ret)

运行程序输出结果如下:

Garbage collector before running: collected 0 objects.
Creating reference cycles...
object with id 14109584 was born
object with id 14109648 was born
[]
Garbage collector after running: collected 4 objects.

gc.garbage返回的是由于循环引用而产生的不可达的垃圾对象的列表,输出为空表示内存中此时不存在垃圾对象。gc.collect()显示所有收集和销毁的对象的数目,此处为4(2个对象A、B,以及其实例属性dict)。

我们再来考虑一个问题:如果我们在类Leak中添加析构方法__del__(),对象的销毁形式和内存回收的情况是否有所不同。示例代码如下:

  def __del__(self):
      print "object with id %d was destoryed" %id(self)

当加入了析构方法__del__()在运行程序的时候会发现gc.garbage的输出不再为空,而是对象A、B的内存地址,也就是说这两个对象在内存中仍然以“垃圾”的形式存在。gc.garbag()输出如下:

[<__main__.Leak object at 0x00D72BF0>, <__main__.Leak object at 0x00D72C30>]

这是什么原因造成的呢?实际上当存在循环引用并且当这个环中存在多个析构方法时,垃圾回收器不能确定对象析构的顺序,所以为了安全起见仍然保持这些对象不被销毁。而当环被打破时,gc在回收对象的时候便会再次自动调用__del__()方法。读者可以自行试验。

gc模块同时支持DEBUG模式,当设置DEBUG模式之后,对于循环引用造成的内存泄露,gc并不释放内存,而是输出更为详细的诊断信息为发现内存泄露提供便利,从而方便程序员进行修复。更多gc模块的使用方法读者可以参考文档:http://docs.python.org/2/library/gc.html。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文