关闭

Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解

标签: python内存泄漏垃圾回收
31334人阅读 评论(4) 收藏 举报
分类:

    Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。引用计数最大缺陷就是循环引用的问题,所以Python采用了辅助方法。本篇文章并不详细探讨Python的垃圾回收机制的内部实现,而是以gc模块为切入点学习Python的垃圾回收机制,如果想深入可以读读<<Python源码剖析>>。

   看如下代码:

import gc
import sys
gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_LEAK)
a=[]
b=[]
a.append(b)
print 'a refcount:',sys.getrefcount(a)  # 2
print 'b refcount:',sys.getrefcount(b)  # 3

del a
del b
print gc.collect()  # 0

输出结果:

a refcount: 2
b refcount: 3
gc: collecting generation 2...
gc: objects in each generation: 0 0 5131
gc: done, 0.0020s elapsed.
0
gc: collecting generation 2...
gc: objects in each generation: 0 0 5125
gc: done, 0.0010s elapsed.

    可以发现垃圾回收不起作用,所以垃圾收集只对循环引用起作用。

你可能好奇,为什么a的引用数是2呢?这时候你需要去看看sys.getrefcount(object)的函数说明了?


哦,该函数Docstring中说返回值通常比我们期望的要多1,因为传给该函数的参数临时变量又增加了一次引用。原来是这样,但让人很奇怪的是,为啥不调整一下呢???

gc.collect()返回此次垃圾回收的unreachable(不可达)对象个数。那什么是unreachable对象呢?请看下面一段代码:

a=[]
b=[]
a.append(b)
b.append(a)
del a
del b
print gc.collect()

输出结果:

gc: collecting generation 2...
gc: objects in each generation: 4 0 5127
gc: collectable <list 02648918>
gc: collectable <list 026488A0>
gc: done, 2 unreachable, 0 uncollectable, 0.0030s elapsed.
2
    此次a,b是循环引用,垃圾回收果然起作用了,回收的两个list的对象,就是a,b,不信可以使用:hex(id(a))输出a的地址。

上面收集的两个都是unreachable对象,那unreachable对象时什么呢?在说明unreachable对象就需要了解Python的标记-清除垃圾回收机制了,简单来说,过程如下:

** 寻找root object集合,root object多指全局引用和函数栈上的引用,如上面代码所示,a就是root object


** 从root object出发,通过其每一个引用到达的所有对象都标记为reachable(垃圾检测)


** 将所有非reachable的对象删除(垃圾回收)


这里还需要提到垃圾回收中的->>可收集对象链表,Python将所有可能产生循环引用的对象用链表连接起来,所谓的可产生循环引用的对象也就是list,dict,class等的容器类,int,string不是,每次实例化该种对象时都将加入这个链表,我们将该链表称为可收集对象链表(ps该链表是双向的)。

如,a=[],b=[],c={},将会产生:head <----> a  <----> b <----> c 双向链表。

  我们可以假想上述代码的垃圾回收过程:当调用gc.collect()时,将从root object开始垃圾回收,由于del a ,del b后,a,b都将成为unreachable对象,且循环引用将被拆除,此时a,b引用数都是0,a,b将被回收,所以collect将返回2。

  看下面一段代码,将加深对上述的理解:

a=[]
b=[]
a.append(b)
b.append(a)
del b
print gc.collect()
输出结果:

gc: collecting generation 2...
gc: objects in each generation: 354 4771 0
gc: done, 0.0010s elapsed.
0
gc: collecting generation 2...
gc: objects in each generation: 0 0 5119
gc: done, 0.0020s elapsed.
   此次并没有垃圾回收,虽然del b了,但从a出发,找到了b的引用,所以b还是reachable对象,所以并不会被收集。


  Python有了垃圾回收机制是否意味着不会造成内存泄漏呢,非也,请看如下代码:

class A:
    def __del__(self):
        pass
class B:
    def __del__(self):
        pass

a=A()
b=B()
print hex(id(a))
print hex(id(a.__dict__))
a.b=b
b.a=a
del a
del b

print gc.collect()
print gc.garbage
输出结果:

0x25cff30
0x25d0b70
gc: collecting generation 2...
gc: objects in each generation: 364 4771 0
gc: uncollectable <A instance at 025CFF30>
gc: uncollectable <B instance at 025CFF58>
gc: uncollectable <dict 025D0B70>
gc: uncollectable <dict 025D0810>
gc: done, 4 unreachable, 4 uncollectable, 0.0020s elapsed.
4
[<__main__.A instance at 0x025CFF30>, <__main__.B instance at 0x025CFF58>, {'b': <__main__.B instance at 0x025CFF58>}, {'a': <__main__.A instance at 0x025CFF30>}]
gc: collecting generation 2...
gc: objects in each generation: 2 0 5127
gc: done, 0.0010s elapsed.
   从输出中我们看到uncollectable字样,很明显这次垃圾回收搞不定了,造成了内存泄漏。

为什么会这样呢?因为del b时,会调用b的__del__方法,该方法中很可能使用了b.a,但如果在之前的del a时将a给回收掉,此时将造成异常。所以Python没办法,造成了uncollectable,也就产生了内存泄漏。所以__del__方法要慎用,如果用的话一定要保证没有循环引用。

   上面我们也打印出了a的地址,print hex(id(a)),也验证了回收的的确是a。

   上面出现了gc.garbage,gc.garbage返回是unreachable对象,且不能被回收的的对象。仔细看看输出结果,为什么貌似有重复???这个困扰了我很久,直到打开gc模块的文档才懂了。由于我们之前gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_LEAK),而gc.DEBUG_LEAK=gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL),文档中指出如果设置了gc.DEBUG_SAVEALL,那么所有的unreachable对象都将加入gc.garbage返回的列表,而不止不能被回收的对象。

   我们看看Python的分代收集机制。

   Python中总共有三个“代”,所谓的三"代”就是三个链表,也就是我们上面所提到的可收集对象链表。当各个代中的对象数量达到一定数量时将触发Python的垃圾回收,各个代的数量如下。


  分代收集的思想就是活的越久的对象,就越不是垃圾,回收的频率就应该越低。所以当Python发现进过几次垃圾回收该对象都是reachable,就将该对象移到二代中,以此类推。那么Python中又是如何检查各个代是否达到阀值的呢?Python中每次会从三代开始检查,如果三代中的对象大于阀值将同时回收3,2,1代的对象。如果二代的满足,将回收2,1代中的对象,设计的是如此的美。


    

10
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Python垃圾回收(gc)拖累了程序执行性能?

Python 起因 前段时间,在做文本处理的实验时,需要预加载大量的原始数据(100W),在Python中使用的字典(dict)类型负责保存这些数据,很快就开发完成 了一个Demo版,然而程序执...
  • overstack
  • overstack
  • 2013-09-12 14:59
  • 3977

python gc模块

gc模块提供一个接口给开发者设置垃圾回收的选项。采用引用计数的方法管理内存的一个缺陷是循环引用的问题,而gc模块的一个主要功能就是 解决循环引用的问题 常用函数: 1.gc.set_debug(f...
  • hzw13816194861
  • hzw13816194861
  • 2017-09-06 15:32
  • 505

Python的内存泄漏及gc模块的使用

Python的内存泄漏及gc模块的使用                  -- 6.11日错误修正版    &...
  • horin153
  • horin153
  • 2007-06-08 17:48
  • 15759

Python的内存管理以及垃圾回收

参考:http://www.cnblogs.com/CBDoctor/p/3781078.html 先从较浅的层面来说,Python的内存管理机制可以从三个方面来讲 (1)垃圾回收 ...
  • onlyanyz
  • onlyanyz
  • 2015-05-09 22:46
  • 5867

如何手动释放Python的内存

在上篇博客中,提到了对一个脚本进行的多次优化。当时以为已经优化得差不多了,但是当测试人员测试时,我才发现,踩到了Python的一个大坑。 在上文的优化中,对每500个用户,会进行一些计算并记录结果在磁...
  • nirendao
  • nirendao
  • 2015-03-18 22:24
  • 32601

Python的垃圾回收机制(二)之内存模型

0. 背景之前介绍了Python垃圾回收的简介,它简要介绍了三种垃圾回收器:引用计数回收器,标记清除回收器和世代回收器,这里会给出Python中基础类型在C语言中的内存模型,以帮助我们理解下一节三种垃...
  • tab_space
  • tab_space
  • 2016-08-27 12:16
  • 1320

python 各种模块学习

0.1. 关于本书0.2. 代码约定0.3. 关于例子0.4. 如何联系我们 核心模块 1.1. 介绍1.2. _ _builtin_ _ 模块1.3. exceptions 模块1.4....
  • weiqubo
  • weiqubo
  • 2017-04-24 10:25
  • 2758

python 中del 的用法

python中的del用法比较特殊,新手学习往往产生误解,弄清del的用法,可以帮助深入理解python的内存方面的问题。 python的del不同于C的free和C++的delete。 由于py...
  • love1code
  • love1code
  • 2015-08-04 18:23
  • 26051

gc垃圾回收的例子

垃圾回收还是没明白,先放这个例子在这里,以后慢慢看。 # -*- coding: utf-8 -*- import gc import sys class CGcLeak(object): ...
  • way88liu
  • way88liu
  • 2014-12-26 11:24
  • 441

Python垃圾回收(gc)拖累了程序执行性能

浅谈Python垃圾回收机制在使用C语言开发时代,我们的开发效率(生产力的问题)受牵制于内存释放、泄露等问题,于是普遍的口号---“指针好难学”。时过境迁,当今的流行的语言都配套了内存自动回收机制,从...
  • aixiaohei
  • aixiaohei
  • 2011-05-26 10:45
  • 6535
    个人资料
    • 访问:1259046次
    • 积分:10280
    • 等级:
    • 排名:第1917名
    • 原创:121篇
    • 转载:11篇
    • 译文:3篇
    • 评论:353条
    博客专栏
    最新评论