[译]Python 内存分配 垃圾回收

原文

Python Garbage Collection

译文

Python主要使用两个策略实现内存分配。

  • 引用计数
  • 垃圾回收

引用计数

统计在系统中,其他对象引用某个对象的次数。当一个引用移除了,这个对象的引用计数减1。引用计数变为0时对象就被回收。

但是引用计数无法解决引用环的问题。引用环,是指某个对象,我们没有办法“够得着”(reach),但是它的引用计数仍然大于0。最简单的制造一个引用环的方法是创造一个自己引用自己的对象。

def make_cycle():
    l = []
    l.append(l)

make_cycle()

因为make_cycle创造了引用自己的对象l,对象l不会被自动释放,直到函数返回。这会造成l使用的内存一直被保存到Python垃圾回收机制被唤起。

对引用环的自动垃圾回收

因为引用环需要花费计算量来发现,垃圾回收必须是一个定时的任务。Python的定时回收计划是根据对象的分配和回收阈值来制定的。当分配数减去回收数大于阈值,垃圾回收就开始运行了。

如果你的Python用完了内存,自动垃圾回收不会运行。

你的应用会抛出异常,这个异常一定要被处理,不然你的应用就崩溃了。这个被家中,因为自动回收机制看中的是闲置对象的数量,而不是他们有多大。因此对于你的代码中任何一个使用了大量内存的部分,最好是手动垃圾回收。

手动垃圾回收

对一些程序而言,尤其是长时间运行的服务器程序,自动回收可能不足够。即使应用写的没有引用环,有策略去处理他们也是好的。在程序运行的适当时间手动唤醒垃圾回收机制是一个好的主意,来处理引用环消耗的内存。

垃圾回收机制可以用以下的方式唤醒:

import gc
gc.collect()

gc.collect()返回被收集和回收的对象的数量。你可以用以下的方式打印出信息:

import gc
collected = gc.collect()
print "Garbage collector: collected %d objects." % (collected)

如果我们创造了一些引用环,我们可以看到手动垃圾收集生效了:

import sys, gc
def make_cycle():
    l = {}
    l[0] = l

def main():
    collected = gc.collect()
    print "Garbage collector: collected %d objects." % (collected)
    print "Creating cycles..."
    for i in range(10):
        make_cycle()
    collected = gc.collect()
    print "Garbage collector: collected %d objects." % (collected)

if __name__ == "__main__":
    ret = main()
    sys.exit(ret)

通常有两种推荐的人工垃圾回收策略:基于时间的和基于事件的。基于时间的垃圾回收很简单:垃圾回收机制在固定的时间间隔被调用。基于事件的垃圾回收机制根据一个事件来调用。比如,当一个用从应用中断开连接或者当一个应用被发现进入了空闲状态。

建议

哪种垃圾回收技术对一个应用而言是正确的?这得看具体情况。垃圾回收机制应该根据需要来回收引用环,而不要影响到重要的应用表现。垃圾回收应该是你的Python应用设计过程的一部分。
1. 不要不受限制地运行垃圾回收,因为它会花费相当多的时间来评估一个大系统的每个使用内存的对象。比如,一个小组遇到内存问题,尝试在复杂启动过程中的每一步都调用gc.collect(),增加了20倍的启动时间。一天运行几次-没有特别的设计原因-很可能是对设备资源的了浪费。
2. 在你的应用完成启动并过渡到稳定运行阶段之后,运行手动垃圾回收。这样可以释放潜在的巨大的内存(被用来打开和解析文件的内存、建立和修改对象列表、甚至是不会再被使用的代码块)例如,一个读取XML配置文件的应用使用了1.5MB的内存。除了人工的垃圾回收,没有方法可以预测什么时候这1.5MB的短暂的内存会被归还给Python内存池重新使用。
3. 在不频繁的使用然后释放了大块内存的代码部分运行人工垃圾回收。举个例子,在一个一天运行一次的任务(评估数以千计的数据点,产生了XML报告,然后通过FTP或者SMTP/email发送报告给中心办公室)之后进行垃圾回收。一个应用做这样的日常报告产生了超过800K的暂时性历史数据的有序列表。在这种日常杂务上捎带上gc.collect()有很好的副作用,因为可以每天“免费”运行它一次。
4. 在对时机要求很严格的代码段的前后,考虑手工运行垃圾回收,来避免垃圾回收打扰了这个重要的时机。比如,一个灌溉程序,可能空闲十分钟,然后评估所有现场设备的状态然后作出调整。因为系统系统调整的延迟会影响现场设备的电池寿命,手动运行垃圾回收是很有意义的。这保证了垃圾回收不会在下一个对时机要求高的时间段被自动触发。

你可能感兴趣的:(python)