在 Python 中,内存管理机制可以被抽象成一个 4 层的层次结构。

在最底层 (第 0 层),是操作系统提供的内存管理接口,Python 不能干涉这一层的行为。

在第 1 层,是 Python 基于第 0 层的接口进行包装之后的。所有的函数都以 PyMem_为前缀。

在第 2 层,是对 Python 中的一些常用对象得内存管理,比如整数对象、字符串对象。

在第 3 层,主要是设计了对象缓冲池机制。

真正在 Python 中发挥巨大作用的,是第 2 层的内存管理机制。

内存管理机制里主要包括 3 个方面:

引用计数

比如某个新建对象,它被分配给某个引用,对象的引用计数变为1,如果引用被删除,对象的引用计数为 0。当这个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。不过如果出现循环引用的话,引用计数机制就无效了。

垃圾回收GC、

解决循环引用的方法:标记清除

标记清除在进行垃圾回收时分两步,分别是:A)标记阶段,遍历所有的对象,如果是可达的,也就是还有对象引用它,那么就标记该对象为可达;

B)清除阶段,再次遍历对象,如果发现某个对象没有标记为可达,则就将其回收。

只有容器对象才会产生循环引用的情况,比如列表、字典、用户自定义类的对象、元组等。而像数字,字符串这类简单类型不会出现循环引用。作为一种优化策略,对于只包含简单类型的元组也不在标记清除算法的考虑之列。

分代回收

在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过“分代回收”(Generational Collection)以空间换时间的方法提高垃圾回收效率。

对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。

GC 给对象定义了三种世代(0,1,2),每一个新生对象在 generation zero 中,如果它在一轮 GC 扫描中活了下来,那么它将被移至 generation one ,在那里他将较少的被扫描,如果它又活过了一轮GC,它又将被移至 generation two ,在那里它被扫描的次数将会更少。

GC 的扫描在当某一世代中被分配的对象与被释放的对象之差达到某一阈值的时候,就会触发GC 对某一世代的扫描。当某一世代的扫描被触发的时候,比该世代年轻的世代也会被扫描。也就是说如果世代 2 的 GC 扫描被触发了,那么世代 0 ,世代 1 也将被扫描,如果世代 1 的 GC 扫描被触发,世代 0 也会被扫描。

结论:通过 “标记-清除” 解决容器对象可能产生的循环引用问题;通过 “分代回收” 以空间换时间的方法提高垃圾回收效率。

内存池

当如果遇到需要频繁的构造对象的情况,就可以由系统统一的构造一批对象,叫做内存池,在需要新的对象的时候直接将已经构造好的对象赋值,在使用完毕后,不是直接析构,而是将它再放回到内存池中,供其他的的对象在构造的时候使用。