Python的内存管理简介


Python使用的垃圾回收方式是引用计数(reference counting)和垃圾回收器(garbage collector), 在2.0版本之前只有引用计数,2.0版本之后带有garbage collector,并且默认开启,可以通过gc模块来进行控制。

Python通过引用计数来管理已分配的内存,引用计数是一个简单的内存管理方式,它的一个缺陷就是循环引用(reference cycles)的问题, 这种情况下多个对象的计数都不会为0,且其他对象无法访问其中的对象,形成一个“孤岛”。实例代码如下:

# 主动删除对象
l = []
l.append(l)
del l

# 变量离开作用域
def make_cycle():
    l = [ ]
    l.append(l)

make_cycle()

循环引用是一个比较难在实践中完全避免的问题,对于长时间运行的服务端程序来说是非常不利的。 为了解决循环引用的问题,需要额外的开销来发现内存中的循环引用并进行回收,通常这类开销都比较大, 会造成停顿,gc的代价较高。

在Python中,循环引用只会出现在container对象中,例如list、dictionary、tuple、class、instance等。

在Python的实现中,设置有一个threshold,作为垃圾回收器的触发条件。 设已分配的对象数量为allocated,释放的对象数量为freed,当满足allocated - freed > threshold的时候, 会启动垃圾回收器,这个threshold有可能会一直没达到,并且和物理内存的大小无关。

可以通过gc模块来获取threshold,代码如下

import gc
print gc.get_threshold()  # (700, 10, 10)

你应该注意到这里的threshold是返回了三个值,后面会介绍Python内部对内存进行了分代(generations)管理的,分别叫作0、1、2,现在先忽略。

当程序内存耗尽(Out of Memory)的时候,自动垃圾回收器(Automatic Garbage Collection)是不会运行的,而是Python解释器会抛出异常MemoryError,可以捕获该异常并进行处理。

垃圾回收的耗时是依赖要释放的对象的个数,而不是要释放对象的大小。基于这个原因,在工程实践中有几点建议:

  1. 循环引用不是一种好的编程实践,在编写代码的时候,应该尽量避免
  2. 在程序中如果一次性释放大量的对象,最好手动执行一次gc(调用gc.collect())
  3. 对于长时间运行的服务程序,最好可以在适当的时机调用一次gc,使得循环引用越少越好(可以定时调度或者在某些服务器空闲的时间段进行调度)

参考资料:

  1. Python Garbage Collection
  2. gc — Garbage Collector interface
  3. Garbage Collection for Python(中文版地址:Python 内存管理方式和垃圾回收算法)
  4. Visualizing Garbage Collection in Ruby and Python
  5. Generational GC in Python and Ruby
  6. Python知识点整理

如果觉得文章对您有帮助,用微信请作者喝杯咖啡吧!这样他会更有动力,分享更多更好的知识!

wechat赞赏