ls0f / my-issues

0 stars 0 forks source link

Python 中的GC #11

Open ls0f opened 8 years ago

ls0f commented 8 years ago

Python中的内存管理方法之一就是引用计数。引用计数非常高效,实现简单。对象被引用时其引用计数加1, 当其不再被一个变量引用时则计数减1. 当引用计数等于0时对象被删除。 比如下面的代码:

def test():
    l = []
    l.append(0)

test函数调用结束后,l对象引用计数减1变为0,会被自动删除。 但引用计数的一个大问题就是不能解决循环引用,比如看下面的代码:

class A:
    pass
def test():
    a = A()
    a.cycle = a

a循环引用自身,因此函数退出后引用计数不为0而是1,对于这样的对象,只能通过其他GC手动来标记并清除。

Python中有gc模块,垃圾回收是自动进行的,你可以通过gc.disable()来禁用自动垃圾回收,手动调用gc.collect()来进行垃圾回收。

gc是个昂贵且费力的事情。垃圾回收时,Python不能进行其它的任务。所以,Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。 可以通过gc模块的get_threshold()方法,查看该阈值。

In [1]: import gc

In [2]: gc.get_threshold()
Out[2]: (700, 10, 10)

700即是垃圾回收启动的阈值,后面的两个10是与分代回收相关的阈值。

Python同时采用了分代(generation)回收的策略。这一策略的假设:存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。

Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。

这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。

可以用set_threshold()来调整,比如对2代对象进行更频繁的扫描。

import gc
gc.set_threshold(700, 10, 5)

Python使用标记清除来处理循环引用问题。可以包含其他对象引用的容器对象(如list, dict, set,甚至class)都可能产生循环引用。Python复制每个容器的引用计数,可以记为gc_ref。然后遍历容器列表,对每个容器引用的对象j,对j的gc_ref减1。在结束遍历后,gc_ref不为0的对象,和这些对象引用的对象,以及继续更下游引用的对象,需要被保留,而其它的对象则被垃圾回收。

参考: http://www.cnblogs.com/vamei/p/3232088.html http://hbprotoss.github.io/posts/pythonla-ji-hui-shou-ji-zhi.html http://stackoverflow.com/questions/4484167/python-garbage-collector-documentation