1. 内存管理层次结构
下图是《Python源码解析》一书中关于内存管理机制的结构图:
第0层,是操作系统提供的内存管理接口;
第1层,是对第0层接口的包装,保证在不同的系统平台上可以有一个统一的接口。实现一组以PyMem_ 为前缀的函数族,它包含两套接口:函数和宏;
第2层,实现以PyObje_为前缀的函数族,主要提供创建Python对象的接口,包括了gc内存管理机制。它对于Python对象的小块内存做优化方案,包括:block、pool、arena 和内存池;
第3层,是对象缓冲池机制,对int, string, list 等常用对象进行内存管理接口的封装,并做优化工作。
2. 内存池的管理
Python 的内存层次结构,从小到大依次为:block -> pool -> arena -> 内存池。第2层的内存管理机制是这样的:
1)Python虚拟机根据需要,每次从操作系统申请一块256KB的内存,取名为 arena。并按系统页大小4K,划分成多个pool,每个pool继续分割成n个大小相同的block,这是内存池中最小的存储单位。一个block大小是8的倍数,也就是说存储13字节大小的对象,需要找block大小为16的pool获取空闲块。所有这些都用头信息和链接管理起来,以便快速查找空闲区域进行分配。
2)如果创建的对象大于256个字节,则直接用malloc在堆上分配内存。程序运行中的绝大多数对象都小于这个阈值,因此内存池策略可有效提升性能。
3)当所有arena的问题超出限制的64M时,就不再请求新的arena内存,而是如同“大对象”一样,直接在堆上为对象分配内存。另外,完全空闲的arena会被释放,其内存交还给操作系统。
3. 引用计数机制
Python默认采用引用计数来管理对象的内存回收,当一个对象的引用计数为0时,将立即回收该对象内存,要么将对应的block块标记为空闲,要反返回给操作系统。
>>> class User(object):
... def __del__(self):
... print "Will be dead!"
>>> a = User()
>>> b = a
>>> import sys
>>> sys.getrefcount(a)
3
>>> del a # 删除引⽤,计数减⼩。
>>> sys.getrefcount(b)
2
>>> del b # 删除最后⼀个引⽤,计数器为 0,对象被回收。
某些内置类型,如小整数,因为缓存的缘故,计数永远不会为0,直到进程结束才由虚拟机清理函数释放。
另外,除了直接引用外,Python 还支持弱引用,弱引用允许在不增加引用计数,不妨碍对象回收的情况下间接引用对象。但不是所有类型都支持弱引用,如 list 、dict ,弱引用会引发异常。
引用计数是⼀种简单直接,并且十分高效的内存回收方式。大多数时候它都能很好地工作,除了循环引用造成计数故障。简单明显的循环引用,可以用弱引用打破循环关系。但在实际开发中,循环引用的形成往往很复杂,可能由 n个对象间接形成一个大的循环体,此时只有靠GC去回收了。
4. 垃圾回收机制
实际上,Python拥有两套垃圾回收机制。除了引用计数,还有个专门处理循环引用的GC。我们提到垃圾回收时,都是指“Reference Cycle Garbage Collection”。
能引用循环引用问题的,都是容器类对象,如: list, set, object等。对于这类对象,虚拟机在为其分配内存时,会额外加上用于追踪的PyGC_Head。这些对象被添加到特殊链表里,以便进行GC管理。
如果代码中不存在循环引用,引用计数的机制被优先执行。当我们能确定这点时,可以关闭GC功能以助于提升性能。
>>> import gc
>>> gc.disable()
Python GC 将回收对象分成3级:GEN0 管理新近加入的年轻对象,GEN1则是在上次回收后依然存活的对象,GEN2管理生命周期极长的对象。每级代龄都有一个最大容量的阈值,每个GEN0对象数量超出阈值时,就将触发垃圾回收操作。
GC首先检查的是GEN2,如果阈值被突破,则合并GEN2, GEN1, GEN0几个追踪链表,如果没有超出,则检查GEN1。GC 将存活的对象提升代龄,而那些回收对象则被打破循环引用,放到专门的列表等待回收。
>>> gc.get_threshold() #获取各级代龄阈值
(700, 10, 10)
>>> gc.get_count() # 各级代龄链接跟踪的对象数量
(454, 8, 2)
但是,包含__del__方法的循环引用对象,永远都不会被GC回收,直到进程结束。__del__ 承担了析构函数的角色,某些时候还是有其特定的作用的。用弱引用回调会造成逻
辑分离,不便于维护。对于一些简单的脚本,我们还是能保证避免循环引用的,那不妨试试。
以上参考雨痕的《Python 学习笔记》、《Python源码剖析》