Python垃圾回收机制
概述
Python的垃圾回收机制采用以引用计数为主,以标记-清除和分代收集为辅的策略
引用计数
原理
每个对象有一个整型的引用计数属性ob_refcnt
,用于记录对象被引用的次数。每当新的引用指向该对象时,它的引用计数ob_refcnt
加一;每当该对象的引用失效时,
他的引用计数ob_refcnt
减一。一旦对象的引用计数为0
,该对象立即被回收,对象占用的内存空间将被释放。
可以使用sys.getrefcount
函数检查该对象的引用计数。(PS: 该函数获取的引用计数总是多1
, 因为该函数在调用时也有对该对象的引用)
计数增减条件
引数加一条件
- 对象被创建
- 对象被引用
- 对象作为函数的参数
- 对象作为容器的元素
引数减一条件
- 对象被显式销毁。例如:
del y
- 变量重新赋予新的对象
- 对象离开它的作用域,常见场景: 变量作为函数的参数。函数执行完成后该对象引用计数减一
- 对象所在的容器被销毁,或从容器中删除对象
优缺点
优点
- 高效、逻辑简单,只需根据规则对计数器做加减法
- 实时性。一旦对象的计数器为零,就说明对象永远不可能再被用到,无须等待特定时机,直接释放内存。
缺点
- 需要为对象分配引用计数空间,增大了内存消耗
- 嵌套对象需要循环调用,耗时长。如字典对象
- 循环引用 => 计数器永不为
0
, 对象无法回收,导致内存泄漏
标记-清除
算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法
一旦无法访问这些对象,要正确处理这些对象,首先需要识别它们。在识别循环的函数内部,维护了两个双向链表:一个列表包含所有要扫描的对象,
另一个将包含所有“暂时”无法访问的对象
GC启动时,它要扫描的所有容器对象都在第一个链表上。目标是移动所有无法到达的对象。由于大多数对象都是可达的,因此移动不可达的对象会更有效,因为这涉及更少的指针更新
标记阶段
对象之间通过指针连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象[全局变量、调用栈、寄存器]出发,沿着有向边遍历对象,
可达的对象标记为活动对象, 不可达的对象就是要被清除的非活动对象。
清除阶段
遍历所有对象,如果发现某个对象没有标记为“可达”,则就回收。
分代收集
分代回收是建立在标记清除技术基础之上
为了限制每次垃圾收集所花费的时间,GC使用了一种流行的优化:generations。这个概念背后的主要思想是假设大多数对象的生命周期都很短,
因此可以在创建后不久就被收集起来。这已被证明非常接近许多 Python 程序的实际情况,因为许多临时对象的创建和销毁速度非常快。
对象越旧,它变得无法访问的可能性就越小。
Python对象被分为三种世代:
- G0: 刚创建的对象
- G1: 如果在一轮
GC
扫描中存活下来,则移至G1
,处于G1
的对象被扫描次数会减少 - G2: 如果再次在扫描中活下来,则进入
G2
,处于G2
的对象被扫描次数将会更少