Python 源码阅读——垃圾回收机制
admin
2023-07-31 00:36:46
0

概述

无论何种垃圾收集机制, 一般都是两阶段: 垃圾检测和垃圾回收.

在Python中, 大多数对象的生命周期都是通过对象的引用计数来管理的.

问题: 但是存在循环引用的问题: a 引用 b, b 引用 a, 导致每一个对象的引用计数都不为0, 所占用的内存永远不会被回收

要解决循环引用: 必需引入其他垃圾收集技术来打破循环引用. Python中使用了标记-清除以及分代收集

即, Python 中垃圾回收机制: 引用计数(主要), 标记清除, 分代收集(辅助)

引用计数

引用计数, 意味着必须在每次分配和释放内存的时候, 加入管理引用计数的动作

引用计数的优点: 最直观最简单, 实时性, 任何内存, 一旦没有指向它的引用, 就会立即被回收

计数存储

回顾 Python 的对象

e.g. 引用计数增加以及减少

1234567891011 >>> from sys import getrefcount>>>>>> a = [1, 2, 3]>>> getrefcount(a)2>>> b = a>>> getrefcount(a)3>>> del b>>> getrefcount(a)2

计数增加

增加对象引用计数, refcnt incr

123   #define Py_INCREF(op) (                               _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA             ((PyObject*)(op))->ob_refcnt++)

计数减少

减少对象引用计数, refcnt desc

1234567891011   #define _Py_DEC_REFTOTAL        _Py_RefTotal–  #define _Py_REF_DEBUG_COMMA     ,   #define Py_DECREF(op)                                         do {                                                          if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA                 ((PyObject*)(op))->ob_refcnt != 0)                          _Py_CHECK_REFCNT(op)                                  else                                                      _Py_Dealloc((PyObject *)(op));                        } while (0)

即, 发现refcnt变成0的时候, 会调用_Py_Dealloc

1234567   PyAPI_FUNC(void) _Py_Dealloc(PyObject *);  #define _Py_REF_DEBUG_COMMA     ,   #define _Py_Dealloc(op) (                                     _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA                (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))  #endif /* !Py_TRACE_REFS */

会调用各自类型的tp_dealloc

例如dict

1234567891011121314151617 PyTypeObject PyDict_Type = {    PyVarObject_HEAD_INIT(&PyType_Type, 0)    \”dict\”,    sizeof(PyDictObject),    0,    (destructor)dict_dealloc,                   /* tp_dealloc */    ....} static voiddict_dealloc(register PyDictObject *mp){    .....    // 如果满足条件, 放入到缓冲池freelist中    if (numfree tp_free((PyObject *)mp);    Py_TRASHCAN_SAFE_END(mp)}

Python基本类型的tp_dealloc, 通常都会与各自的缓冲池机制相关, 释放会优先放入缓冲池中(对应的分配会优先从缓冲池取). 这个内存分配与回收同缓冲池机制相关

当无法放入缓冲池时, 会调用各自类型的tp_free

int, 比较特殊

12 // int, 通用整数对象缓冲池机制      (freefunc)int_free,                         /* tp_free */

string

12 // string    PyObject_Del,                               /* tp_free */

dict/tuple/list

1     PyObject_GC_Del,                            /* tp_free */

然后, 我们再回头看, 自定义对象的tp_free

123456 PyTypeObject PyType_Type = {    PyVarObject_HEAD_INIT(&PyType_Type, 0)    \”type\”,                                     /* tp_name */    ...    PyObject_GC_Del,                            /* tp_free */};

即, 最终, 当计数变为0, 触发内存回收动作. 涉及函数PyObject_DelPyObject_GC_Del, 并且, 自定义类以及容器类型(dict/list/tuple/set等)使用的都是后者PyObject_GC_Del.

内存回收 PyObject_Del / PyObject_GC_Del

如果引用计数=0:

12 1. 放入缓冲池2. 真正销毁, PyObject_Del/PyObject_GC_Del内存操作

这两个操作都是进行内存级别的操作

  • PyObject_Del

PyObject_Del(op) releases the memory allocated for an object. It does not
run a destructor — it only frees the memory. PyObject_Free is identical.

这块删除, PyObject_Free 涉及到了Python底层内存的分配和管理机制, 具体见前面的博文

  • PyObject_GC_Del

1234567891011121314   void  PyObject_GC_Del(void *op)  {      PyGC_Head *g = AS_GC(op);       // Returns true if a given object is tracked      if (IS_TRACKED(op))          // 从跟踪链表中移除          gc_list_remove(g);      if (generations[0].count > 0) {          generations[0].count;      }      PyObject_FREE(g);  }

IS_TRACKED 涉及到标记-清除的机制

generations 涉及到了分代回收

PyObject_FREE, 则和Python底层内存池机制相关

标记-清除

问题: 什么对象可能产生循环引用?

只需要关注关注可能产生循环引用的对象

PyIntObject/PyStringObject等不可能

Python中的循环引用总是发生在container对象之间, 所谓containser对象即是内部可持有对其他对象的引用: list/dict/class/instance等等

垃圾收集带来的开销依赖于container对象的数量, 必需跟踪所创建的每一个container对象, 并将这些对象组织到一个集合中.

可收集对象链表

可收集对象链表: 将需要被收集和跟踪的container, 放到可收集的链表中

任何一个python对象都分为两部分: PyObject_HEAD + 对象本身数据

12345678910111213 /* PyObject_HEAD defines the initial segment of every PyObject. */#define PyObject_HEAD                       _PyObject_HEAD_EXTRA                    Py_ssize_t ob_refcnt;                   struct _typeobject *ob_type; //—————————————————-   #define _PyObject_HEAD_EXTRA                  struct _object *_ob_next;                 struct _object *_ob_prev; // 双向链表结构, 垃圾回收

可收集对象链表

1234567

相关内容

热门资讯

500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
python绘图库Matplo... 本文简单介绍了Python绘图库Matplotlib的安装,简介如下: matplotlib是pyt...
Prometheus+Graf... 一,Prometheus概述 1,什么是Prometheus?Prometheus是最初在Sound...