Python 的垃圾回收回收机制(源码)

python内存管理及垃圾回收

1. 引用计数器

1.1 环状双向连表 refchain

在python程序中创建的任何对象都会放在refchain链表中,并且可以通过这个对象访问到上一个和下一个对象。

name = '张三'
age = 18
hobby = ['美女','吃饭']
内部会建立一些数据 -打包 C语言叫做结构体-> 【上一个对象、下一个对象、类型、引用个数】
name = "张三"   # 创建一个对象开辟内存空间
new = name  # new指向指向"张三"这块内存,不会重新分配内存,引用计数+1

内部会建立一些数据 【上一个对象、下一个对象、类型、引用个数、val=18】
age = 18   # int会添加具体值

内部会建立一些数据 【上一个对象、下一个对象、类型、引用个数、items=元素、元素个数】
hobby = ['美女','吃饭']  # 列表会添加元素和元素个数

C语言源码

  • 每个对象都有相同的值:PyObject 结构体 (4个值)。
  • 有多个元素组成的对象:PyObject 结构体 (4个值) + ob_size 。
#define PyObject_HEAD       PyObject ob_base;
#define PyObject_VAR_HEAD      PyVarObject ob_base;

// 宏定义,包含 上一个、下一个,用于构造双向链表。(放到refchain链表中时会用到)
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;
     
// 结构体
typedef struct _object {
    _PyObject_HEAD_EXTRA // 用于构造双向链表
    Py_ssize_t ob_refcnt;  // 引用计数器
    struct _typeobject *ob_type;    // 数据类型
} PyObject;
 
typedef struct {
    PyObject ob_base;   // PyObject对象
    Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */
} PyVarObject;

源码解析,包含:

  • 2个结构体
    • PyObject,此结构体中包含3个元素。
      • _PyObject_HEAD_EXTRA,用于构造双向链表。
      • ob_refcnt,引用计数器。
      • *ob_type,数据类型。
    • PyVarObject,次结构体中包含4个元素(ob_base中包含3个元素)
      • ob_base,PyObject结构体对象,即:包含PyObject结构体中的三个元素。
      • ob_size,内部元素个数。
  • 3个宏定义
    • PyObject_HEAD,代指PyObject结构体。
    • PyVarObject_HEAD,代指PyVarObject对象。
    • _PyObject_HEAD_EXTRA,代指前后指针,用于构造双向队列。

1.2 不同类型封装的结构体

# float类型
data = 3.14

内部会创建:
	_ob_next = refchain 中的上一个对象
	_ob_prev = refchain 中的下一个对象 
	ob_refcnt = 1 
	ob_type = float
	ob_fval = 3.14

C源码:

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

1.3 引用计数器

v1 = 3.14
v2 = 999
v3 = [1,2,3]

当python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段进行创建相关的数据,然后将对象添加到refchain双向链表中。

在C源码中有两个关键的结构体:PyObject、PyVarObject。

每个对象中有ob_refcnt 就是引用计数器,默认值为1,当有其他变量引用对象时,引用计数器就会发生变化。

  • 引用

    a = 999
    b = a    # 999 这个对象的引用计数器+1,为2
    
  • 删除引用

    a = 999
    b = a 
    del b  # b变量删除,b对应的对象的引用计数器 -1
    del a  # a变量删除,a对应的对象的引用计数器 -1 (a、b对应同一个对象)
    
    """
    当一个对象的引用计数器为0时,意味着没有人可以使用这个对象了,这个对象就是垃圾,需要被回收
    回收:
    	1.对象从refchain链表中移除
    	2.将对象销毁,内存归还系统
    
    """
    

    1.5 循环引用

在这里插入图片描述

2. 标记清除

目的:为了解决引用计数器循环引用的不足。

实现:在python的底层 再维护一个链表,链表中专门放那些可能存在循环引用的对象 (list/tuple/dict/set)。
在这里插入图片描述

在python内部 某种情况下 触发,会去扫描 可能存在循环引用的链表中的每一个元素,检查是否有循环引用,如果有则双方的引用计数器 -1,如果是 0 则垃圾回收。

问题:

  • 什么时候扫描?
  • 可能存在循环引用的链表扫描代价较大,每次扫描耗时久。

3. 分代回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K55asNPj-1605967623657)(E:\07-notes\picture\58_分待回收图在这里插入图片描述
.png)]

将可能存在循环引用的对象维护成 3个链表:

  • 0代:0代中对象个数达到700个扫描一次;
  • 1代:0代扫描 10 次,则 1代扫描一次;
  • 2代:1代扫描10次,则 2 代扫描一次。

4. 小结

在python中维护了一个refchain的双向环状链表,这个链表中存储程序中创建的所有对象,每种类型的对象都有一个 ob_refcnt 的引用计数器的值,对象被引用,则计数器的值 +1,引用被删除则计数器的值 -1,最后引用计数的值为 0 时,会进行垃圾回收(对象销毁、从refchain中移除),

但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用问题,为了解决这个问题python引入了 标记清除 ,在其内部再维护了一个链表,专门放那些可能存在循环引用的对象 (list/tuple/dict/set), 某种情况下 触发,会去扫描 可能存在循环引用的链表中的每一个元素,检查是否有循环引用,如果有则双方的引用计数器 -1,如果是 0 则垃圾回收,

然而,又有一个新的问题产生,就是什么时候扫描?可能存在循环引用的链表扫描代价较大,每次扫描耗时久,所以又引入了 分代回收 ,将可能存在循环引用对象维护成 3 个链表,分别是 0代,1代,2代,所有可能存在循环引用的对象都存储在 0代链表,当对象个数达到700个的时候扫描一次,是垃圾则回收,不是则移代 1代,依次类推,0代扫描10次,1代扫描一次,1代扫描10次,2代扫描1次。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值