Shihanmax's blog

< Back

Python垃圾回收机制(GC)

Python中的GC机制:

  • 以引用计数为主
  • 分代回收为辅

python对象的核心是一个结构体:PyObject

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct_object {
    int ob_refcnt;
    struct_typeobject *ob_type;
} PyObject;

#def Py_INCREF(op) ((op)->ob_refcnt++)
#def Py_DECREF(op)

if (--(op)->ob_refcnt != 0)
    ;
else
    __Py_Dealloc((PyObject *)(op))

ob_refcnt 就是为了做引用计数,当一个对象的引用为0时,会被清除。

Python在合适的时机会对处在链表中的有循环引用的对象引用减1,这样,原本有引用的对象不会被清除,而原本循环引用的对象,其引用计数被置零并回收。

零代链中的无引用对象,将剩余有引用对象挪到一代链,对一代链同理。这三代链子被清理的频率:零代 » 一代 » 二代

1
2
3
4
5
6
7
>>> import gc

>>> gc.get_count()  # 查看当前隔代回收当前状态
(154, 5, 3)
>>> gc.get_threshold()  # 新创建的减去释放掉的如果大于此阈值,触发分代回收
(700, 10, 10)  # 10和10表示:清理10次0代链表后清理一代链表,清理10次一代链表后清理一次二代链表。
>>> gc.collect()  # 显式执行垃圾回收

Python和Ruby的标记-清除机制对比:

​ Ruby:一次性创建大量可用对象,用完后标记清除。

​ Python:初始化时才创建对象,一旦引用为0立即清除。

一点注意

如果类的__del__方法被重写(未调用父类的del方法),则执行垃圾回收时无法回收该对象。

引用计数相关

导致引用计数 +1 的情况

  • 对象被创建
  • 对象被引用
  • 对象被传入函数中
  • 对象作为一个元素存放在容器中

导致引用计数 -1 的情况:

  • 对象被 del 显式销毁
  • 对象被赋予新的对象
  • 一个对象离开其作用域(如已经执行完毕的函数中的形参)
  • 对象所在的容器被销毁

查看对象的引用计数:

1
2
3
4
import sys

a = 'Hello world'
sys.getrefcount(a)

整数对象池

小整数对象池

为了避免整数的频繁申请和销毁内存空间,Python使用了小整数对象池。

[-5, 257)内的整数都在小整数对象池中,他们都已经被提前建立好了,常驻内存,不被回收。

大整数对象池

每一个大整数的定义都会创建一个新的对象。

字符串共享机制(intern)
1
2
3
4
5
>>> a1 = 'HelloWorld'
>>> a2 = 'HelloWorld'
>>> a3 = 'HelloWorld'
>>> id(a1) == id(a2) == id(a3)
True

a1-a5拥有共同的id(实际指向了同一块内存),但如果字符串中有特殊字符(如空格),则不会触发共享机制共享。