Python源码到内存管理机制(笔记版)
创始人
2025-05-28 07:46:09
0

介绍Python的内存管理机制,先从 Python 的对象布局讲起,以最简单的浮点数为例,在 Include/floatobject.h 中,python 中的浮点数是这么定义的:

typedef struct {PyObject_HEADdouble ob_fval;
} PyFloatObject;

我们继续查看 PyObject_HEAD 的定义:

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   \_PyObject_HEAD_EXTRA                \Py_ssize_t ob_refcnt;               \struct _typeobject *ob_type;

这是一个宏定义,而其中的 EXTRA 在正常编译时是空的。所以,我们直接展开所有宏,那么 PyFloatObject 的定义就是这样子的:

typedef struct {Py_ssize_t ob_refcnt;struct _typeobject *ob_type;double ob_fval;
} PyFloatObject;

这样就很清楚了,ob_refcnt 就是引用计数,而 ob_fval 是真正的值。例如我写一段这样的代码:

a = 1000.0
a = 2000.0

在执行第 1 句时,Python 虚拟机真正执行的逻辑是创建一个 PyFloatObject 对象,然后使它的 ob_fval 为 1000.0,同时,它的引用计数为 1;当执行到第 2 句时,创建一个值为 2000.0 的 PyFloatObject 对象,并且使这个对象的引用计数为 1,而前一个对象的引用计数就要减 1,从而变成 0。那么前一个对象就会被回收。

在 Python 中,引用计数的维护是通过这两个宏来实现的:

#define Py_INCREF(op) (                         \_Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \((PyObject*)(op))->ob_refcnt++)
#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)

这两个宏位于 Include/object.h 中。这段代码里最重要的地方在于 ob_refcnt 增一和减一的操作。

使用了引用计数的地方,就会存在循环引用。例如下图中的四个对象,A 是根对象,它与 B 之间有循环引用,那么它们都不是垃圾对象。C 和 D 之间也有循环引用,但因为没有外界的引用指向它们了,所以它们就是垃圾对象,但是循环引用导致他们都不能释放。
在这里插入图片描述

Python 为了解决这个问题,在虚拟机中引入了一个双向链表,把所有对象都放到这个链表里。Python 的每个对象头上都有一个名为 PyGC_Head 的结构:

/* GC information is stored BEFORE the object structure. */
typedef union _gc_head {struct {union _gc_head *gc_next;union _gc_head *gc_prev;Py_ssize_t gc_refs;} gc;long double dummy;  /* force worst-case alignment */
} PyGC_Head;

在这个结构里,gc_next 和 gc_prev 的作用就是把对象关联到链表里。而 gc_refs 则是用于消除循环引用的。当链表中的对象达到一定数目时,Python 的 GC 模块就会执行一次标记清除。具体来讲,一共有四步。

第一步,将 ob_refcnt 的值复制到 gc_refs 中。对于上面的例子,它们的 gc_refs 的值就如下图所示:
在这里插入图片描述

第二步是遍历整个链表,对每个对象,将它直接引用的对象的 gc_refs 的值减一。比如遍历到 A 对象时,只把 B 对象的 gc_refs 值减一;遍历到 B 对象时,再把它直接引用的 A 对象的 gc_refs 值减一。经过这一步骤后,四个对象的 gc_refs 的值如下图所示:
在这里插入图片描述

第三步,将 gc_refs 值为 0 的对象,从对象链表中摘下来,放入一个名为“临时不可达”的链表中。之所以使用“临时”,是因为有循环引用的垃圾对象的 gc_refs 在此时一定为 0,比如 C 和 D。但 gc_refs 值为 0 的对象不一定是垃圾对象,比如 B 对象。此时,B、C 和 D 对象就被放入临时不可达链表中了,示意图如下所示:
在这里插入图片描述

最后一步,以可达对象链表中的对象为根开始深度优先搜索,将所有访问到 gc_refs 为 0 的对象,再从临时不可达链表中移回可达链表中。最后留在临时不可达链表中的对象,就是真正的垃圾对象了。

接下来就可以使用 _Py_Dealloc 逐个释放链表中的对象了,对于上面的例子,就是把 B 对象重新加回到可达对象链表中,然后将 C 和 D 分别释放。

扩展资料

  • 博客园:Python内存管理机制
  • 知乎:Python 内存管理机制(史上最通俗易懂)
  • 博客园:Python3 源码阅读 - 内存管理机制
  • InforQ:深入理解 Python 内存管理与垃圾回收

相关内容

热门资讯

月儿圆优秀诗歌欣赏 月儿圆优秀诗歌欣赏  月儿圆  朋友突然发消息给我,  让我看窗外。  我晓得,  今晚的月儿很圆。...
燕南飞爱情诗歌 燕南飞爱情诗歌  一曲弹唱,能解开我几多愁。  一首相思,独独的我心守候。  我傻傻地等以为你伫立原...
儿童现代诗歌 儿童现代诗歌大全  在生活、工作和学习中,大家一定都接触过一些使用较为普遍的诗歌吧,诗歌具有精炼含蓄...
席慕容的爱情诗 席慕容的爱情诗  无论是在学校还是在社会中,大家都对那些朗朗上口的诗歌很是熟悉吧,诗歌饱含着作者的思...
离别时请不要说再见诗歌 离别时请不要说再见诗歌  站在寂静的湖畔,  感受初秋的美好、  秋风中,也不时透着丝丝的寒凉。  ...
2021建党100周年诗歌初... 时光如水,岁月如梭,转眼间中国共产党已走过了一百年的光辉历程,中国社会也在党领导人民的长期奋斗中,发...
现代诗歌朗诵 现代诗歌朗诵大全  在现实生活或工作学习中,大家都收藏过令自己印象深刻的诗歌吧,诗歌以强烈的节奏、美...
感恩诗歌朗诵稿 感恩诗歌朗诵稿(精选14篇)  在社会一步步向前发展的今天,大家总免不了要接触或使用朗诵稿吧,朗诵者...
章水与贡水现代诗歌 章水与贡水现代诗歌  章水与贡水  在美丽的八镜台幽会  从此牵手  一路缠绵  恩爱浩荡千里  章...
最全的《诗经》名句精选 《诗经》是中国最古老的一部诗歌总集,一部现实主义的诗歌总集,它收录从西周初年到春秋中叶即公元前110...
雨的诗歌 关于雨的诗歌集锦  在日复一日的学习、工作或生活中,大家都经常接触到诗歌吧,诗歌具有音韵和谐,节奏鲜...
杨宇七夕快乐诗歌 杨宇七夕快乐诗歌  【七夕】七夕节,又名乞巧节、七巧节或七姐诞,发源于中国,是华人地区以及部分受汉族...
关于春天的诗歌怎么写6篇 在学习、工作或生活中,大家都看到过许多经典的诗歌吧,诗歌具有精炼、集中,节奏鲜明,富有韵律的特点。还...
家园情结诗歌 家园情结诗歌  一  梦里萦绕,情牵何处?  绿树掩映,垂柳依依绰绰;群山紧拥,冉阳烂漫。  月色盈...
心盲诗歌 心盲诗歌  ——故去的将永逝  存在的要永恒  世间有两双眼,  一双观察表象,  一双审视本质。 ...
描写蓝天白云的诗歌及成语 关于描写蓝天白云的诗歌及成语  在日常的学习、工作、生活中,许多人都接触过一些比较经典的诗歌吧,诗歌...
适合朗诵短诗歌经典 适合朗诵短诗歌经典  诗歌是一种抒情言志的文学体裁。一般来说它的语言都是高度凝练的,下面是关于适合朗...
军训诗歌   军训,我们哭过,我们笑过,我们累过,我们苦过。但这就是无悔的青春。本文将介绍2016军训诗歌。 ...
仿写艾青我爱这土地5篇 艾青早期的诗歌,以现实主义手法,深沉忧郁地唱出了祖国的土地和人民所遭受的苦难和不幸,反映了中华民族的...
与月亮的诗歌 与月亮有关的诗歌(精选6首)  无论是身处学校还是步入社会,大家都收藏过自己喜欢的诗歌吧,诗歌语言言...