Python有两种共存的内存管理机制: 引用计数和垃圾回收.
引用计数是一种非常高效的内存管理手段, 当一个Python对象被引 用时其引用计数增加1, 当其不再被一个变量引用时则计数减1. 当引用计数等于0时对象被删除.
引用计数
import gc
import objgraph
gc.disable()
class A(object):
pass
class B(object):
pass
def test1():
a = A()
b = B()
test1()
print objgraph.count('A')
print objgraph.count('B')
上面程序的执行结果为:
Object count of A: 0
Object count of B: 0
在test1中, 我们分别创建了类A和类B的对象, 并用变量a, b引用起来.
当test1调用结束后objgraph.count('A')返回0, 意味着内存中A的对象数量没有增长. 同理B的对象数量也没有增长.
注意我们通过gc.disable()关闭了 Python的垃圾回收, 因此test1中生产的对象是在函数调用结束引用计数为0时被自动删除的.
继续上面的代码:
def test2():
a = A()
b = B()
a.child = b
b.parent = a
test2()
print 'Object count of A:', objgraph.count('A')
print 'Object count of B:', objgraph.count('B')
gc.collect()
print 'Object count of A:', objgraph.count('A')
print 'Object count of B:', objgraph.count('B')
在上面的代码的执行结果为:
Object count of A: 1
Object count of B: 1
Object count of A: 0
Object count of B: 0
test1相比test2的改变是将A和B的对象通过child和parent相互引用了起来. 这就形成了一个循环引用.
当test2调用结束后, 表面上我们不再引用两个对象, 但由于两个对象相互引用着对方, 因此引用计数不为0, 则不会被自动回收. 更糟糕的是由于现在没有任何变量引用他们, 我们无法再找到这两个变量并清除.
Python使用垃圾回收机制来处理这样的情况. 执行gc.collect(), Python垃圾回收器就会回收两个相互引用的对象, 之后A和B的对象数又变为0.
垃圾回收
在Python中, 所有能够引用其他对象的对象都被称为容器(container). 因此只有容器之间才可能形成循环引用.
Python的垃圾回收机制利用了这个特点来寻找需要被释放的对象. 为了记录下所有的容器对象, Python将每一个容器都链到了一个双向链表中, 有了这个维护了所有容器对象的双向链表以后, Python在垃圾回收时使用如下步骤来寻找需要释放的对象:
1.对于每一个容器对象, 设置一个gc_refs值, 并将其初始化为该对象的引用计数值.
2.对于每一个容器对象, 找到所有其引用的对象, 将被引用对象的gc_refs值减1.
3.执行完步骤2以后所有gc_refs值还大于0的对象都被非容器对象引用着, 至少存在一个非循环引用. 因此不能释放这些对象, 将他们放入另一个集合.
4.在步骤3中不能被释放的对象, 如果他们引用着某个对象, 被引用的对象也是不能被释放的, 因此将这些对象也放入另一个集合中.
5.此时还剩下的对象都是无法到达的对象. 现在可以释放这些对象了.
值得注意的是, 如果一个Python对象含有__del__这个方法, Python的垃圾回收机制即使发现该对象不可到达也不会释放他. 这里就不细说了。
除此之外, Python还将所有对象根据’生存时间’分为3代, 从0到2.
所有新创建的对象都分配为第0代. 当这些对象经过一次垃圾回收仍然存在则会被放入第1代中. 如果第1代中的对象在一次垃圾回收之后仍然存货则被放入第2代.
对于不同代的对象Python的回收的频率也不一样. 可以通过gc.set_threshold(threshold0[, threshold1[, threshold2]]) 来定义.
当Python的垃圾回收器中新增的对象数量减去删除的对象数量大于threshold0时, Python会对第0代对象执行一次垃圾回收. 每当第0代被检查的次数超过了threshold1时, 第1代对象就会被执行一次垃圾回收. 同理每当第1代被检查的次数超过了threshold2时, 第2代对象也会被执行一次垃圾回收.
由于Python的垃圾回收需要检查所有的容器对象, 因此当一个Python程序生产了大量的对象时, 执行一次垃圾回收将带来较大的开销.