通常来说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。 只有当引用计数的值为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
模块来进行垃圾回收,代码如下:
import pprint
import sys
def main():
collected = gc.collect()
print("Garbage collector before running collected %d object." %(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 object." %(collected))
ret = main()
# sys.exit(ret)
Garbage collector before running collected 6 object. Creating reference cycles... object with id 140508184684512 was born object with id 140508178666400 was born [] Garbage collector after running collected 2 object.
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 OxOOD72BF0>, <__main__.Leak object at 0xO0D72C3O>]
这是什么原因造成的呢?实际上当存在循环引用并且当这个环中存在多个析构方法时,
垃圾回收器不能确定对象析构的顺序,所以为了安全起见仍然保持这些对象不被销毁,
而当环被打破时,gc
在回收对象的时候便会再次自动调用__del__()
方法。
读者可以自行试验。
gc
模块同时支持 DEBUG
模式,当设置 DEBUG
模式之后,对于循环引用造成的内存泄露,
gc
并不释放内存,而是输出更为详细的诊断信息为发现内存泄露提供便利,
从而方便程序员进行修复。更多gc模块的使用方法读者可以参考文桂:http://docs.python.org/2Zlibrary/gc.html。