python的垃圾回收机制:
主要是引用计数为主,分代回收为辅。
一、引用计数
引用计数的原理:
// object.h
struct _object {
Py_ssize_t ob_refcnt; # 引用计数值
struct PyTypeObject *ob_type;
} PyObject
每个对象维护一个ob_ref。用来记录当前对象被引用的次数,也就是用来追踪到底有多少引用指向了这个对象。
当发生以下四种情况,该对象的引用计数+1
1、对象被创建 a = 10
2、对象被引用 b = a
3、对象被作为参数,传到函数中 func(a)
当发生以下四种情况,该对象的引用计数-1
1、当该对象的别名被显示销毁时 del a
2、当该对象的引别名被赋予新的对象 a = 26
3、一个对象离开它的作用域,例如 func函数执行完毕时,函数里面的局部变量的引用计数器就会减一(全部变量不会)
4、将该元素从容器中删除时,或者容器被销毁时。
当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机立即销毁
引用计数法的优点:
1.高效
2.实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
3.对象有确定的生命周期
4.易于实现
原始的引用计数法也有明显的缺点:
1.无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数都为1,但显然应该被回收。
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
为了解决这两个致命弱点,Python又引入了以下两种GC机制。
二、标记-清除
Python的引用计数算法不能够处理互相指向自己的对象。Python的GC不能够处理未使用的对象因为应用计数值不会到零。
这就是为什么Python要引入Generational GC算法的原因!
『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
在上图中,我们把小黑点作为全局变量,也就是root object,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
“标记-清除”法是为了解决循环引用问题。
因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
Python处理零代最为频繁,其次是一代然后才是二代。
然后根据引用计数副本值是否为0将集合内的对象分成两类,reachable和unreachable,其中unreachable是可以被回收的对象:
三、分代回收
先给出gc的逻辑
分配内存
->发现超过阈值了
->触发垃圾回收
->将所有可收集对象链接放到一起
->遍历,计算有效引用计数
->分成有效引用计数=0和有效引用计数 > 0两个集合
->大于0的,放入到更老一代
->=0的执行回收
->回收遍历容器内的各个元素,减掉对应元素引用计数(砍掉循环引用)
->执行-1的逻辑,若发现对象引用计数=0,触发内存回收
->python底层内存管理机制回收内存
Python中, 引入了分代收集, 总共三个”代”. Python 中, 一个代就是一个链表, 所有属于同一”代”的内存块都链接在同一个链表中
用来表示“代”的结构体是gc_generation, 包括了当前代链表表头、对象数量上限、当前对象数量:
// gcmodule.c
struct gc_generation {
PyGC_Head head;
int threshold; /* collection threshold */
int count; /* count of allocations or collections of younger generations */
};
新生成的对象会被加入第0代,前面_PyObject_GC_Malloc中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满,如果满了就开始着手进行垃圾回收.
分代回收总结:
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象.
参考文献
原文链接:https://blog.csdn.net/xiongchengluo1129/article/details/80462651