python的垃圾回收

目录

  • python的垃圾回收
  • 一、白话垃圾回收
  • 1、大管家refchain
  • 2、引用计数器
  • 3、标记清除&分代回收
  • 4、情景模拟
  • 5、缓存机制
  • 二、C语⾔源码分析
  • 1、两个重要的结构体
  • 2、常⻅类型结构体
  • 3、Float类型
  • 4、int类型
  • 一、白话垃圾回收

    通俗理解什么是内存管理和垃圾回收的过程

    1、大管家refchain

    在Python的C源码中有⼀个名为refchain的环状双向链表,这个链表⽐较⽜逼了,因为Python程序中⼀旦创建

    对象都会把这个对象添加到refchain这个链表中。也就是说他保存着所有的对象。例如:

    age = 18
    name = ”武沛齐“
    

    =

    2、引用计数器

    在refchain中的所有对象内部都有⼀个ob_refcnt⽤来保存当前对象的引⽤计数器,顾名思义就是⾃⼰被引⽤的次数,例如:

    age = 18
    name = "武沛⻬"
    nickname = name
    

    上述代码表示内存中有 18 和 “武沛⻬” 两个值,他们的引⽤计数器分别为:1、2 。

    当值被多次引⽤时候,不会在内存中重复创建数据,⽽是引⽤计数器+1 。 当对象被销毁时候同时会让引⽤计数器-1,如果引⽤计数器为0,则将对象从refchain链表中摘除,同时在内存中进⾏销毁(暂不考虑缓存等特殊情况)

    age = 18  # 对象 18 的引用计数器为 1
    number = age  # 对象 18 的引用计数器 +1,现在为 2
    del age  # 删除变量 age,引用计数器 -1,现在为 1
    
    def run(arg):
        print(arg)  # 临时对对象 18 增加一个引用计数器
        # 函数执行完毕后,引用计数器恢复
    
    run(number)  # 刚开始执行函数时,引用计数器 +1,函数结束后引用计数器 -1
    
    num_list = [11, 22, number]  # 对象 18 的引用计数器 +1,现在为 2
    
    

    3、标记清除&分代回收

    基于引⽤计数器进⾏垃圾回收⾮常⽅便和简单,但他还是存在循环引⽤的问题,导致⽆法正常的回收⼀些数据,例如:

    v1 = [11, 22, 33]  # 创建列表对象 [11, 22, 33],引用计数为 1(v1 引用它)
    v2 = [44, 55, 66]  # 创建列表对象 [44, 55, 66],引用计数为 1(v2 引用它)
    
    v1.append(v2)  # 将 v2 对应的列表 [44, 55, 66] 添加到 v1 中,v2 引用计数 +1,变为 2
    v2.append(v1)  # 将 v1 对应的列表 [11, 22, 33] 添加到 v2 中,v1 引用计数 +1,变为 2
    
    del v1  # 删除变量 v1,对 [11, 22, 33] 的引用计数 -1,但仍然被 v2 引用,引用计数变为 1
    del v2  # 删除变量 v2,对 [44, 55, 66] 的引用计数 -1,但仍然被 v1 引用,引用计数变为 1
    

    Python 使用 引用计数 作为垃圾回收的主要方式,同时还有一个 分代垃圾回收器,用来处理循环引用的问题。当循环引用发生时,引用计数无法回收这些对象,但垃圾回收器可以通过标记清除的方式回收它们。

    对于上述代码会发现,执⾏del操作之后,没有变量再会去使⽤那两个列表对象,但由于循环引⽤的问题,他们的引⽤计数器不为0,所以他们的状态:永远不会被使⽤、也不会被销毁。项⽬中如果这种代码太多,就会导致内存⼀直被消耗,直到内存被耗尽,程序崩溃。

    为了解决循环引⽤的问题,引⼊了标记清除技术,专⻔针对那些可能存在循环引⽤的对象进⾏特殊处理,可能存在循环应⽤的类型有:列表、元组、字典、集合、⾃定义类等那些能进⾏数据嵌套的类型。

    标记清除:创建特殊链表专⻔⽤于保存 列表、元组、字典、集合、⾃定义类等对象,之后再去检查这个链表中的对象是否存在循环引⽤,如果存在则让双⽅的引⽤计数器均 – 1 。

    分代回收:对标记清除中的链表进⾏优化,将那些可能存在循引⽤的对象拆分到3个链表,链表称为:0/1/2三代,每代都可以存储对象和阈值,当达到阈值时,就会对相应的链表中的每个对象做⼀次扫描,除循环引⽤各⾃减1并且销毁引⽤计数器为0的对象

    // 分代的C源码
    #define NUM_GENERATIONS 3 表示 Python 的垃圾回收分为 3 代(第 0 代、第 1 代、第 2 代)。
    
    struct gc_generation {
        uintptr_t head[2];  // 代链表头节点
        int threshold;      // 回收阈值
        int count;          // 回收计数
    };
    
    # generations 是一个数组,表示各代的垃圾回收信息。
    # 数组中每一项对应一个代:
    # 第 0 代:管理新创建的对象。第 1 代:管理从第 0 代幸存下来的对象。第 2 代:管理从第 1 代再次幸存的对象。
    struct gc_generation generations[NUM_GENERATIONS] = {
        /* PyGC_Head, threshold, count */
        {{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0},  // 第 0 代
        {{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0},   // 第 1 代
        {{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0},   // 第 2 代
    };
    # 第 0 代的阈值为 700,表示当第 0 代的对象达到 700 个时触发垃圾回收。
    # 第 1 代和第 2 代的阈值为 10。
    

    特别注意:0代和1、2代的threshold和count表示的意义不同。

    ·代,count表示0代链表中对象的数量,threshold表示0代链表对象个数阈值,超过则执⾏⼀次0代扫描检查。

    ·1代,count表示0代链表扫描的次数,threshold表示0代链表扫描的次数阈值,超过则执⾏⼀次1代扫描检查。

    ·2代,count表示1代链表扫描的次数,threshold表示1代链表扫描的次数阈值,超过则执⾏⼀2代扫描检查。

    分代回收的优点在于:将垃圾回收的频率集中在短命的对象上,从而提高了性能。

    4、情景模拟

    根据C语⾔底层并结合图来讲解内存管理和垃圾回收的详细过程。

    第⼀步:当创建对象age=19时,会将对象添加到refchain链表中

    第⼆步:当创建对象num_list = [11,22]时,会将列表对象添加到 refchain 和 generations 0代中。

    第三步:新创建对象使generations的0代链表上的对象数量⼤于阈值700时,要对链表上的对象进⾏扫描检查。

    当0代⼤于阈值后,底层不是直接扫描0代,⽽是先判断2、1是否也超过了阈值。

    ​ ·如果2、1代未达到阈值,则扫描0代,并让1代的 count + 1 。

    ​ ·如果2代已达到阈值,则将2、1、0三个链表拼接起来进⾏全扫描,并将2、1、0代的count重置为0.

    ​ ·如果1代已达到阈值,则讲1、0两个链表拼接起来进⾏扫描,并将所有1、0代的count重置为0.

    对拼接起来的链表在进⾏扫描时,主要就是剔除循环引⽤和销毁垃圾,详细过程为:

    ​ ·扫描链表,把每个对象的引⽤计数器拷⻉⼀份并保存到 gc_refs中,保护原引⽤计数器。

    ​ ·再次扫描链表中的每个对象,并检查是否存在循环引⽤,如果存在则让各⾃的gc_refs减 1 。

    ​ ·再次扫描链表,将 gc_refs 为 0 的对象移动到unreachable链表中;不为0的对象直接升级到下⼀代链表中。

    ​ ·处理unreachable链表中的对象的 析构函数 和 弱引⽤,不能被销毁的对象升级到下⼀代链表,能销毁的保留在此链表

    析构函数,指的就是那些定义了__del__⽅法的对象,需要执⾏之后再进⾏销毁处理。

    ​ ·最后将 unreachable 中的每个对象销毁并在refchain链表中移除(不考虑缓存机制)。⾄此,垃圾回收的过程结束。

    5、缓存机制

    从上⽂⼤家可以了解到当对象的引⽤计数器为0时,就会被销毁并释放内存。⽽实际上他不是这么的简单粗暴,因为反复的创建和销毁会使程序的执⾏效率变低。Python中引⼊了“缓存机制”机制。

    例如:引⽤计数器为0时,不会真正销毁对象,⽽是将他放到⼀个名为 free_list 的链表中,之后会再创建对象时不会在重新开辟内存,⽽是在free_list中将之前的对象来并重置内部的值来使⽤。

    ​ ·float类型,维护的free_list链表最多可缓存100个float对象。

    # 创建一个浮点数对象,并将其添加到refchain链表
    v1 = 3.14  # 为float对象开辟内存,将其引用添加到refchain链表
    print(id(v1))  # 打印对象的内存地址,例如:4436033488
    
    # 删除v1变量的引用
    del v1  # 引用计数器 -1,如果引用计数器为0,移除refchain链表。
            # 对象未销毁,而是被添加到float类型的free_list缓存池中。
    
    # 创建另一个浮点数对象
    v2 = 9.999  # 优先尝试从float类型的free_list中获取对象。
                 # 如果free_list中有缓存对象,则重用并重置为9.999;
                 # 如果free_list为空,则重新开辟内存。
    
    print(id(v2))  # 打印对象的内存地址,例如:4436033488
    
    # 注意:
    # 当对象的引用计数器为0时,系统会检查float类型的free_list:
    # - 如果free_list未满,则将对象缓存到free_list。
    # - 如果free_list已满,则直接销毁对象并释放内存。
    

    ​ ·int类型,不是基于free_list,⽽是维护⼀个small_ints链表保存常⻅数据(⼩数据池),⼩数据池范围:-5 <= value < 257。即:重复使⽤这个范围的整数时,不会重新开辟内存。

    # 从小数据池 small_ints 中获取整数 38 对象
    v1 = 38  # small_ints 池中已经存在值为 38 的整数对象。
             # 将 small_ints 中的该对象添加到 refchain,并将其引用计数器 +1。
    print(id(v1))  # 打印对象的内存地址,例如:4514343712
    
    # 再次从 small_ints 中获取整数 38 对象
    v2 = 38  # small_ints 中已经存在 38 的整数对象,
             # 因此不会重新创建对象,而是复用该对象,将引用计数器 +1。
    print(id(v2))  # 打印对象的内存地址,和 v1 的地址相同,例如:4514343712
    
    # 注意:
    # 1. 在 Python 解释器启动时,整数范围 -5 至 256 的对象会被提前加载到 small_ints 池中,
    #    并将其引用计数器初始化为 1。
    # 2. 当代码中使用 -5 至 256 范围内的整数时,会直接从 small_ints 池中获取,
    #    并将引用计数器 +1。
    # 3. small_ints 中的整数对象的引用计数器永远不会归零,因为它们在解释器生命周期内始终可用,
    #    所以不会被销毁。
    

    ​ ·str类型,维护unicode_latin1[256]链表,内部将所有的ascii字符缓存起来,以后使⽤时就不再反复创建。

    # 示例 1:单字符字符串驻留
    v1 = "A"  # 创建字符串 "A",将其添加到字符串驻留池中
    print(id(v1))  # 输出:4517720496(示例地址,仅供参考)
    del v1  # 删除 v1 变量,对象引用计数器减 1,但驻留的字符串 "A" 仍在驻留池中
    
    v2 = "A"  # 再次创建 "A",Python 会直接复用驻留池中的字符串对象
    print(id(v2))  # 输出:4517720496(与 v1 相同的地址)
    
    # 注:
    # Python 对含字母、数字、下划线的短字符串使用驻留机制,
    # 这些字符串在首次创建时会存入驻留池,后续会直接复用已有对象。
    
    # 示例 2:驻留机制的字符串复用
    v1 = "wupeiqi"  # 创建字符串 "wupeiqi",存入驻留池
    v2 = "wupeiqi"  # Python 检测到驻留池中已存在相同字符串,直接复用对象
    print(id(v1) == id(v2))  # 输出:True,说明两个变量引用同一对象
    
    # 驻留机制特点:
    # 1. 针对只含字母、数字、下划线的短字符串。
    # 2. 驻留的字符串不需要像 free_list 那样始终驻留内存,
    #    但只要在内存中有相同的字符串,就可以被复用。
    
    

    ​ ·list类型,维护的free_list数组最多可缓存80个list对象。

    # 示例 1:创建和删除列表对象
    v1 = [11, 22, 33]  # 创建一个列表对象,内存分配给该对象
    print(id(v1))  # 输出 v1 的内存地址(示例地址,仅供参考)
    
    del v1  # 删除变量 v1,列表对象的引用计数器减 1。如果引用计数器变为 0,内存可能被回收。
    
    # 示例 2:创建新的列表对象
    v2 = ["武", "沛齐"]  # 创建另一个列表对象
    print(id(v2))  # 输出 v2 的内存地址,可能与 v1 的地址相同
    
    # 注意:
    # - Python 的内存管理依赖引用计数,同时结合垃圾回收机制。
    # - 当一个对象的引用计数器归零时,其占用的内存可能会被回收。
    # - 新创建的对象可能会复用刚释放的内存地址(具体是否复用取决于垃圾回收器的行为)。
    

    ​ ·tuple类型,维护⼀个free_list数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。元组的free_list数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list数组中对应的链表,并添加到链表中。

    # 示例:元组对象的缓存与复用
    v1 = (1, 2)  # 创建一个元组对象,元组的大小为 2
    print(id(v1))  # 输出 v1 的内存地址
    
    del v1  # 删除变量 v1,元组对象引用计数减 1
    # 如果元组大小小于等于 20,Python 会将其缓存到 `free_list` 对应的链表中,而不是立即销毁。
    
    v2 = ("武沛齐", "Alex")  # 创建一个新的元组对象,大小也是 2
    print(id(v2))  # 输出 v2 的内存地址,可能与 v1 相同(因为内存可能被复用)
    
    # 注意:具体行为可能因 Python 版本而异,尤其是对象缓存与内存复用的实现细节。
    

    ​ ·dict类型,维护的free_list数组最多可缓存80个dict对象。

    # 示例:字典对象的内存复用
    v1 = {"k1": 123}  # 创建一个字典对象
    print(id(v1))  # 输出 v1 的内存地址
    
    del v1  # 删除 v1,字典对象的引用计数减 1
    # 字典对象通常不会被缓存,但内存可能被释放并复用。
    
    v2 = {"name": "武沛齐", "age": 18, "gender": "男"}  # 创建一个新的字典对象
    print(id(v2))  # 输出 v2 的内存地址,可能与 v1 相同(如果内存地址被复用)
    
    # 注意:字典对象的内存复用不是 Python 明确保证的行为,而是可能由内存分配机制决定。
    

    二、C语⾔源码分析

    1、两个重要的结构体

    // 定义 PyObject 头部结构
    #define PyObject_HEAD PyObject ob_base;
    #define PyObject_VAR_HEAD PyVarObject ob_base;
    
    // 宏定义:构造双向链表所需的字段,表示上一个和下一个对象指针
    #define _PyObject_HEAD_EXTRA \
        struct _object *_ob_next; /* 指向链表中的下一个对象 */ \
        struct _object *_ob_prev; /* 指向链表中的上一个对象 */
    
    // 定义 PyObject 结构体,用于表示所有 Python 对象的基础结构
    typedef struct _object {
        _PyObject_HEAD_EXTRA // 双向链表指针
        Py_ssize_t ob_refcnt; // 引用计数器
        struct _typeobject *ob_type; // 指向该对象的类型信息
    } PyObject;
    
    // 定义 PyVarObject 结构体,用于表示可变大小的 Python 对象
    typedef struct {
        PyObject ob_base; // 继承 PyObject 的基础结构
        Py_ssize_t ob_size; // 变量部分的元素数量
    } PyVarObject;
    
    

    这两个结构体PyObject和PyVarObject是基⽯,他们保存这其他数据类型公共部分,例如:每个类型的对象在创建时都有PyObject中的那4部分数据;list/set/tuple等由多个元素组成对象创建时都有PyVarObject中的那5部分数据。

    宏定义 PyObject_HEADPyObject_VAR_HEAD:

  • PyObject_HEAD 定义了所有 Python 对象的基础部分,包括引用计数和类型信息。
  • PyObject_VAR_HEADPyObject 的基础上添加了 ob_size 字段,用于记录可变大小对象的元素数量。
  • 双向链表支持:

  • _PyObject_HEAD_EXTRA 宏定义了两个指针 _ob_next_ob_prev,用于构建引用链表(refchain),方便管理 Python 对象。
  • 基础对象结构 (PyObject):

  • ob_refcnt 是引用计数器,表示该对象当前被引用的次数。
  • ob_type 指向对象的类型信息,标明对象的具体类型。
  • 可变对象结构 (PyVarObject):

  • PyObject 的基础上增加了 ob_size,专用于描述包含多个元素的对象(如列表、元组、字符串等)。
  • 2、常⻅类型结构体

    平时我们在创建⼀个对象时,本质上就是实例化⼀个相关类型的结构体,在内部保存值和引⽤计数器等。

    ​ ·float类型

    typedef struct {
        PyObject_HEAD // 包含对象的基础信息,如引用计数和类型信息
        double ob_fval; // 用于存储浮点数的具体值
    } PyFloatObject;
    

    ​ ·int类型

    struct _longobject {
        PyObject_VAR_HEAD // 用于支持变长对象的宏
        digit ob_digit[1]; // 存储整数的数组,支持任意精度
    };
    
    /* Long (arbitrary precision) integer object interface */
    typedef struct _longobject PyLongObject; // 定义长整型对象类型
    
    

    ​ ·str类型

    typedef struct {
        PyObject_HEAD
        Py_ssize_t length;  /* 字符串中的字符数 */
        Py_hash_t hash;  /* 哈希值;未设置时为 -1 */
        struct {
            unsigned int interned:2;
            /* 字符大小:
             - PyUnicode_WCHAR_KIND (0):
               * 字符类型 = wchar_t (16 或 32 位,取决于平台)
             - PyUnicode_1BYTE_KIND (1):
               * 字符类型 = Py_UCS1 (8 位,无符号)
               * 所有字符的范围在 U+0000 到 U+00FF 之间 (latin1)
               * 如果设置了 ascii,所有字符的范围在 U+0000 到 U+007F 之间 (ASCII),否则至少有一个字符在 U+0080 到 U+00FF 之间
             - PyUnicode_2BYTE_KIND (2):
               * 字符类型 = Py_UCS2 (16 位,无符号)
               * 所有字符的范围在 U+0000 到 U+FFFF 之间 (BMP)
               * 至少有一个字符在 U+0100 到 U+FFFF 之间
             - PyUnicode_4BYTE_KIND (4):
               * 字符类型 = Py_UCS4 (32 位,无符号)
               * 所有字符的范围在 U+0000 到 U+10FFFF 之间
               * 至少有一个字符在 U+10000 到 U+10FFFF 之间
            */
            unsigned int kind:3;
            unsigned int compact:1;
            unsigned int ascii:1;
            unsigned int ready:1;
            unsigned int :24;
        } state;
        wchar_t *wstr;  /* wchar_t 表示的字符串(以 null 终止) */
    } PyASCIIObject;
    
    typedef struct {
        PyASCIIObject _base;
        Py_ssize_t utf8_length;  /* utf8 编码的字节数,不包括终止的 \0 */
        char *utf8;  /* UTF-8 编码表示(以 null 终止) */
        Py_ssize_t wstr_length;  /* wstr 中的字符数,可能有替代字符算作两个字符 */
    } PyCompactUnicodeObject;
    
    typedef struct {
        PyCompactUnicodeObject _base;
        union {
            void *any;
            Py_UCS1 *latin1;
            Py_UCS2 *ucs2;
            Py_UCS4 *ucs4;
        } data;  /* 标准化、最小形式的 Unicode 缓冲区 */
    } PyUnicodeObject;
    
    

    ​ ·list类型

    typedef struct {
        PyObject_VAR_HEAD
        PyObject **ob_item;  /* 列表中的元素指针数组 */
        Py_ssize_t allocated;  /* 已分配的空间大小 */
    } PyListObject;
    

    ​ ·tuple类型

    typedef struct {
        PyObject_VAR_HEAD
        PyObject *ob_item[1];  /* 元组中的元素指针数组 */
    } PyTupleObject;
    

    ​ ·dict类型

    typedef struct {
        PyObject_HEAD
        Py_ssize_t ma_used;  /* 字典中使用的键值对数量 */
        PyDictKeysObject *ma_keys;  /* 指向字典键对象的指针 */
        PyObject **ma_values;  /* 指向字典值的指针数组 */
    } PyDictObject;
    

    通过常⻅结构体可以基本了解到本质上每个对象内部会存储的数据。

    为python字符串在处理时需要考虑到编码的问题,在内部规定(⻅源码结构体):

    ​ ·字符串只包含ascii,则每个字符⽤1个字节表示,即:latin1

    ​ ·字符串包含中⽂等,则每个字符⽤2个字节表示,即:ucs2

    ​ ·字符串包含emoji等,则每个字符⽤4个字节表示,即:ucs4

    image-20250103162258293

    3、Float类型

    创建

    val = 3.14
    

    类似于这样创建⼀个float对象时,会执⾏C源码中的如下代码:

    // Objects/floatobject.c
    // 用于缓存 float 对象的链表
    static PyFloatObject *free_list = NULL;  // 空闲 float 对象链表
    static int numfree = 0;  // 当前空闲对象的数量
    
    PyObject *
    PyFloat_FromDouble(double fval)
    {
        // 如果 free_list 中有可用对象,则从 free_list 链表中取出一个;否则为对象重新分配内存。
        PyFloatObject *op = free_list;
        if (op != NULL) {
            free_list = (PyFloatObject *) Py_TYPE(op);  // 更新 free_list 指向下一个空闲对象
            numfree--;  // 空闲对象数量减一
        } else {
            // 根据 float 类型的大小,为 float 对象新分配内存。
            op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
            if (!op)
                return PyErr_NoMemory();  // 如果内存分配失败,返回内存不足错误
        }
    
        // 对 float 对象进行初始化,例如:引用计数器初始化为 1、添加到 refchain 链表等。
        /* 内联执行 PyObject_New */
        (void)PyObject_INIT(op, &PyFloat_Type);
    
        // 对 float 对象赋值,即:op->ob_fval = fval
        op->ob_fval = fval;
    
        return (PyObject *) op;  // 返回创建的 float 对象
    }
    
    
    // Include/objimpl.h
    // 初始化 PyObject 的宏定义
    #define PyObject_INIT(op, typeobj) \
     ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
    
    // Objects/object.c
    // 维护了所有对象的一个环状双向链表
    static PyObject refchain = {&refchain, &refchain};  // 初始化 refchain 链表
    
    void
    _Py_AddToAllObjects(PyObject *op, int force)
    {
        // 如果 force 为真或对象未在链表中(op->_ob_prev 为 NULL),将对象添加到 refchain 链表中
        if (force || op->_ob_prev == NULL) {
            op->_ob_next = refchain._ob_next;  // 设置新对象的下一个节点为 refchain 的下一个节点
            op->_ob_prev = &refchain;  // 设置新对象的前一个节点为 refchain
            refchain._ob_next->_ob_prev = op;  // 更新 refchain 原下一个节点的前向指针为新对象
            refchain._ob_next = op;  // 更新 refchain 的下一个节点为新对象
        }
    }
    
    void
    _Py_NewReference(PyObject *op)
    {
        _Py_INC_REFTOTAL;  // 增加全局引用计数总数
        op->ob_refcnt = 1;  // 引用计数器初始化为 1
        _Py_AddToAllObjects(op, 1);  // 将对象添加到双向链表 refchain 中
        _Py_INC_TPALLOCS(op);  // 增加类型分配统计计数
    }
    
    

    引用

    val = 3.14
    data = val
    

    在项⽬中如果出现这种引⽤关系时,会将原对象的引⽤计数器+1。

    C源码执⾏流程如下

    // Include/object.h
    static inline void _Py_INCREF(PyObject *op)
    {
        _Py_INC_REFTOTAL;  // 增加全局引用计数总数
        // 对象的引用计数器 +1
        op->ob_refcnt++;
    }
    
    // 将 Py_INCREF 宏定义为 _Py_INCREF 的封装,适配 _PyObject_CAST 的类型转换
    #define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
    

    销毁

    val = 3.14
    del val
    

    在项⽬中如果出现这种删除的语句,则内部会将引⽤计数器-1,如果引⽤计数器减为0,则进⾏缓存或垃圾回收。

    C源码执⾏流程如下:

    // Include/object.h
    static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
    {
        (void)filename;  // 可能未使用,关闭 -Wunused-parameter 警告
        (void)lineno;    // 可能未使用,关闭 -Wunused-parameter 警告
        _Py_DEC_REFTOTAL;  // 减少全局引用计数总数
    
        // 引用计数器 -1,如果引用计数器为 0,则执行 _Py_Dealloc 进行缓存回收或垃圾回收
        if (--op->ob_refcnt != 0) {
    #ifdef Py_REF_DEBUG
            if (op->ob_refcnt < 0) {
                _Py_NegativeRefcount(filename, lineno, op);  // 如果引用计数小于 0,记录负引用计数错误
            }
    #endif
        } else {
            _Py_Dealloc(op);  // 引用计数为 0,释放对象
        }
    }
    
    // 将 Py_DECREF 宏定义为 _Py_DECREF 的封装,自动记录调用文件和行号,并适配类型转换
    #define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
    
    // Objects/object.c
    void
    _Py_Dealloc(PyObject *op)
    {
        // 找到对象类型的 tp_dealloc 函数,用于对象的回收处理
        destructor dealloc = Py_TYPE(op)->tp_dealloc;
        // 在 refchain 双向链表中移除此对象
        _Py_ForgetReference(op);
        // 调用 tp_dealloc 函数执行具体的缓存或垃圾回收操作
        (*dealloc)(op);
    }
    
    void
    _Py_ForgetReference(PyObject *op)
    {
        ...
        // 在 refchain 双向链表中移除此对象
        op->_ob_next->_ob_prev = op->_ob_prev;  // 更新后一个节点的前向指针
        op->_ob_prev->_ob_next = op->_ob_next;  // 更新前一个节点的后向指针
        op->_ob_next = op->_ob_prev = NULL;    // 将当前节点的指针清空
        _Py_INC_TPFREES(op);  // 增加类型释放统计计数
    }
    
    
    // Objects/floatobject.c
    
    // 定义 float 类型对象缓存的最大数量
    #define PyFloat_MAXFREELIST 100
    
    // 当前缓存中的 float 对象数量
    static int numfree = 0;
    
    // float 对象的缓存链表
    static PyFloatObject *free_list = NULL;
    
    // 定义 float 类型对象的函数对应关系
    PyTypeObject PyFloat_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "float",                      // 类型名称
        sizeof(PyFloatObject),        // 对象的大小
        0,
        // tp_dealloc 表示执行 float_dealloc 方法
        (destructor)float_dealloc,    /* tp_dealloc */
        0,                            /* tp_print */
        ...
    };
    
    // 定义 float 类型对象的释放函数
    static void
    float_dealloc(PyFloatObject *op)
    {
        // 检测对象是否是 float 类型
        if (PyFloat_CheckExact(op)) {
            // 检测缓存链表是否已满,如果已满,则直接销毁对象
            if (numfree >= PyFloat_MAXFREELIST) {
                // 销毁对象
                PyObject_FREE(op);
                return;
            }
            // 将对象加入缓存链表 free_list
            numfree++;
            Py_TYPE(op) = (struct _typeobject *)free_list;  // 将 free_list 的头赋值为对象类型
            free_list = op;  // 将对象加入链表头
        } else {
            // 如果对象不是 float 类型,则调用其类型的 tp_free 方法释放内存
            Py_TYPE(op)->tp_free((PyObject *)op);
        }
    }
    
    

    4、int类型

    创建

    age = 19
    

    当在python中创建⼀个整型数据时,底层会触发他的如下源码

    PyObject *
    PyLong_FromLong(long ival)
    {
        PyLongObject *v;
        ...
        // 优先检查是否在小整数池中,如果在范围内则直接获取,而无需重新开辟内存。(-5 <= value < 257)
        CHECK_SMALL_INT(ival);
        ...
        // 如果值不在小整数池范围内,则重新开辟内存并初始化
        v = _PyLong_New(ndigits);
        if (v != NULL) {
            digit *p = v->ob_digit;
            Py_SIZE(v) = ndigits * sign;
            t = abs_ival;
            ...
        }
        return (PyObject *)v;
    }
    
    #define NSMALLNEGINTS 5      // 小整数池中存储的负整数数量
    #define NSMALLPOSINTS 257    // 小整数池中存储的正整数数量
    
    // 检查是否在小整数池范围内的宏定义
    #define CHECK_SMALL_INT(ival) \
        do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
            return get_small_int((sdigit)ival); \
        } while(0)
    
    // 获取小整数池中的对象
    static PyObject *
    get_small_int(sdigit ival)
    {
        PyObject *v;
        // 从小整数池中获取对应的整数对象
        v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
        // 引用计数器 +1,确保对象不会被回收
        Py_INCREF(v);
        ...
        return v;
    }
    
    // 创建一个新的长整型对象
    PyLongObject *
    _PyLong_New(Py_ssize_t size)
    {
        // 创建 PyLongObject 的指针变量
        PyLongObject *result;
        ...
        // 根据数字长度分配内存
        result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) +
                                 size * sizeof(digit));
        ...
        // 初始化内存中的数据,并将对象添加到 refchain 链表中
        return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);
    }
    
    
    // 定义一个宏,用于创建可变大小的对象
    #define PyObject_NewVar(type, typeobj, n) \
        ( (type *) _PyObject_NewVar((typeobj), (n)) )
    
    // 初始化一个可变大小对象(PyVarObject)
    static inline PyVarObject*
    _PyObject_INIT_VAR(PyVarObject *op, PyTypeObject *typeobj, Py_ssize_t size)
    {
        assert(op != NULL);  // 确保对象指针不为空
        Py_SIZE(op) = size;  // 设置对象的大小(数组大小或动态部分的大小)
        // 调用 PyObject_INIT 进行基础对象初始化
        PyObject_INIT((PyObject *)op, typeobj);
        return op;
    }
    
    // 定义一个宏,用于基础对象初始化
    #define PyObject_INIT(op, typeobj) \
        _PyObject_INIT(_PyObject_CAST(op), (typeobj))
    
    // 内联函数,用于基础对象的初始化
    static inline PyObject*
    _PyObject_INIT(PyObject *op, PyTypeObject *typeobj)
    {
        assert(op != NULL);  // 确保对象指针不为空
        Py_TYPE(op) = typeobj;  // 设置对象的类型为指定的类型对象
        // 检查类型对象是否是堆分配类型
        if (PyType_GetFlags(typeobj) & Py_TPFLAGS_HEAPTYPE) {
            Py_INCREF(typeobj);  // 如果是堆分配类型,增加类型对象的引用计数
        }
        // 完成对象初始化,并将其加入全局对象链表(refchain)
        _Py_NewReference(op);
        return op;
    }
    
    
    // 全局变量 refchain:一个环状双向链表,用于跟踪所有 Python 对象
    static PyObject refchain = {&refchain, &refchain};
    
    // 将对象添加到全局对象链表 refchain 中
    void _Py_AddToAllObjects(PyObject *op, int force)
    {
        // 如果需要强制添加或对象未在链表中(op->_ob_prev == NULL)
        if (force || op->_ob_prev == NULL) {
            op->_ob_next = refchain._ob_next; // 新对象的下一个指向 refchain 的下一个
            op->_ob_prev = &refchain;        // 新对象的上一个指向 refchain
            refchain._ob_next->_ob_prev = op; // 更新原链表第一个节点的上一个为新对象
            refchain._ob_next = op;          // 更新 refchain 的下一个为新对象
        }
    }
    
    // 初始化新对象的引用计数,并将其加入 refchain 链表
    void _Py_NewReference(PyObject *op)
    {
        _Py_INC_REFTOTAL;        // 增加全局引用计数统计值
        op->ob_refcnt = 1;       // 初始化对象的引用计数为 1
        _Py_AddToAllObjects(op, 1); // 将对象添加到 refchain 链表中
        _Py_INC_TPALLOCS(op);    // 增加分配对象的统计值
    }
    
    

    引用

    value = 69
    data = value
    

    类似于出现这种引⽤关系时,内部其实就是将对象的引⽤计数器+1,源码同float类型引⽤。

    销毁

    value = 699
    del value
    

    在项⽬中如果出现这种删除的语句,则内部会将引⽤计数器-1,如果引⽤计数器减为0,则直接进⾏垃圾回收。

    (int类型是基于⼩数据池⽽不是free_list做的缓存,所以不会在销毁时缓存数据)。

    C源码执⾏流程如下:

    // Include/object.h
    // 减少对象引用计数的函数
    static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
    {
        // 以下两行是为了防止未使用的参数触发编译警告
        (void)filename; 
        (void)lineno;
    
        _Py_DEC_REFTOTAL; // 全局引用计数统计值减少 1
    
        // 减少对象的引用计数
        if (--op->ob_refcnt != 0) {
    #ifdef Py_REF_DEBUG
            // 如果引用计数变为负值,触发调试警告
            if (op->ob_refcnt < 0) {
                _Py_NegativeRefcount(filename, lineno, op);
            }
    #endif
        } else {
            // 如果引用计数为 0,则执行对象回收
            _Py_Dealloc(op);
        }
    }
    
    // 宏定义:简化 _Py_DECREF 的调用,同时记录文件名和行号
    #define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
    
    
    // Objects/object.c
    void
    _Py_Dealloc(PyObject *op)
    {
        // 找到int类型的 tp_dealloc 函数(int类中没有定义tp_dealloc函数,需要去⽗级PyBaseObject_Type中找tp_dealloc函数)
        // 此处体现所有的类型都继承object
        destructor dealloc = Py_TYPE(op)->tp_dealloc;
        // 在refchain双向链表中摘除此对象。
        _Py_ForgetReference(op);
        // 执⾏int类型的 tp_dealloc 函数,去进⾏垃圾回收。
        (*dealloc)(op);
    }
    
    void
    _Py_ForgetReference(PyObject *op)
    {
        ...
        // 在refchain链表中移除此对象
        op->_ob_next->_ob_prev = op->_ob_prev;
        op->_ob_prev->_ob_next = op->_ob_next;
        op->_ob_next = op->_ob_prev = NULL;
        _Py_INC_TPFREES(op);
    }
    
    
    // Objects/longobjet.c
    PyTypeObject PyLong_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "int", /* tp_name */
        offsetof(PyLongObject, ob_digit), /* tp_basicsize */
        sizeof(digit), /* tp_itemsize */
        0, /* tp_dealloc */
        ...
        PyObject_Del, /* tp_free */
    };
    
    
    // Objects/typeobject.c
    PyTypeObject PyBaseObject_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "object", /* tp_name */
        sizeof(PyObject), /* tp_basicsize */
        0, /* tp_itemsize */
        object_dealloc, /* tp_dealloc */
        ...
        PyObject_Del, /* tp_free */
    };
    
    static void
    object_dealloc(PyObject *self)
    {
        // 调用int类型的 tp_free,即:PyObject_Del去销毁对象。
        Py_TYPE(self)->tp_free(self);
    }
    
    

    其余str,tuple,dict,list等不再多说

    作者:Long韵韵

    物联沃分享整理
    物联沃-IOTWORD物联网 » python的垃圾回收

    发表回复