热门搜索 :
考研考公
您的当前位置:首页正文

Python中的垃圾回收机制!惊艳了!

2024-07-16 来源:伴沃教育

1. 引用计数器

1.1 环状双向链表 refchain

在python程序中创建的任何对象都会放在refchain链表中。

#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;
name = "阿玮"
age = 18
hobby = ["健身", "美女"]
内部会创建一些数据 [ 上一个对象、下一个对象、类型、引用个数 ]
name = "阿玮"
new = name      # 引用个数变成2
内部会创建一些数据 [ 上一个对象、下一个对象、类型、引用个数、val=18 ]
age = 18
内部会创建一些数据 [ 上一个对象、下一个对象、类型、引用个数、items=元素、元素个数 ]
hobby = ["健身", "美女"]
#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;

在C源码中如何体现每个对象中都有的相同的值:PyObject结构体(4个值)。

有多个元素组成的对象:PyObject结构体(4个值)+ ob_size = PyVarObject。

1.2 类型封装结构体

float类型

typedef struct {
    PyObject_HEAD;
    double ob_fval;
};
data = 3.14;
内部会创建:
    _ob_next = refchain中的下一个对象
    _ob_prev = refchain中的上一个对象
    ob_refcnt = 1
    ob_type = float
    ob_fval = 3.14

int类型

struct _longobect {
    PyObject_VAR_HEAD;
    digit ob_dit[1];
};
/* Long (arbitrary precision) integer object interface */
typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */

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;
    PyDictKeyObject *ma_keys;
    PyObject **ma_values;
} PyDictObject;

1.3 引用计数器

v1 = 3.14
v2 = 999
v3 = (1,2,3)

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

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

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

引用

a = 99999
b = a
# 此时 99999 这个对象引用计数器的值为2
'''
下面情况会导致引用计数器+1:
    1.对象被创建,如 a = 2
    2.对象被引用,如 b = a
    3.对象被作为参数,传入到一个函数中
    4.对象作为一个元素,存储在容器中
可以通过sys包中的getrefcount()来获取一个名称所引用的对象当前的引用计数器的值(注意这里getrefcount()本身会使得引用计数器+1)
'''

删除引用

a = 99999
b = a
# b变量删除,b对应对象的引用计数器-1
def b
# a变量删除,a对应对象的引用计数器-1
'''
下面情况会导致引用计数器-1:
    1.变量被显示销毁 del
    2.变量被赋予新的对象
    3.一个对象离开它的作用域
    4.对象所在的容器被销毁或从容器中删除对象
'''
# 当一个对象的引用计数器为0时,意味着没有人再使用这个对象了,这个对象就是垃圾,垃圾回收。
# 回收:1.对象从rechain链表移出。2.将对象销毁,内存归还。

1.4 循环引用问题

由于 v1 指向的对象引用了 v2,v2 指向的对象也引用了 v1,当将 v1、v2 两个变量删除时,虽然引用计数器会减1,但是两个对象间还存在循环引用,而此时已经没有变量能去指向它们,这两个对象就会在内存中常驻无法处理。

2. 标记清除

目的:为了解决引用计数器循环引用的问题。

实现:在python的底层再维护一个链表,链表中专门放哪些可能存在循环应用的对象(容器类对象:list、tuple、dict、set)。

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

2.1 标记阶段

遍历所有对象,如果是可达的(reachable),也就是还有对象引用它,那么就将该对象标记为可达

该阶段从某个对象开始扫描(而不是从变量),如果变量A引用了变量B,则将变量B的引用计数器-1(指的是gc_ref),然后扫描变量B

如图所示,link1、link2、link3形成了一个引用环,link4自引用。从link1开始扫描,link1引用了link2,则link2的gc_ref-1,接着扫描link2…

像这也将链表中所有对象考察一遍后,两个链表中的对象ref_count和gc_ref,这一步操作就相当于解除了循环引用对引用计数器的影响

如果gc_ref为0,则将对象标记为 GC_TENTATIVELY_UNREACHABLE,并且被移至”Unreachable“链表中,如下图link3、link4(我觉得link2应该也是)

如果gc_ref不为0,那么这个对象会被标记为可达的GC_REACHABLE,同时当gc发现有一个节点是可达的,那么它会递归式的从该节点触发将所有可达的节点标记为GC_REACHABLE,这样把link2、link3救回来

2.2 清除阶段

将被标记成 GC_UNREACHABLE 的对象销毁,内存归还(也就是Unreachable链表中的对象)

2.3 标记清除的问题

在标记清除算法开始后,会暂停整个应用程序,等待标记清除结束后才会恢复应用的运行,且对循环引用的扫描代价大,每次扫描耗时可能很久

3. 分代回收

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

0代:0代中对象个数达到700个扫描一次

1代:0代扫描10次,则1代扫描一次

2代:1代扫描10次,则2代扫描一次

4. 小结

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

但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用的问题,为了解决这个问题,python又引入了标记清除和分代回收,在其内部维护了4个链表,分别为:

refchain

2代

1代

0代

在源码内部,当达到各自的阈值时,就会触发扫描链表进行标记清除的动作(有循环引用则各自-1)。

But,源码内部在上述流程中提出了优化机制。

伴沃教育还为您提供以下相关内容希望对您有帮助:

python垃圾收集机制?

Python的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。当一个对象的引用计数变为0,解释器会暂停,释放掉这个对象和仅有这个对象可访问(可到达)的其他对象。作为引用计数的补充,垃圾收集器也会留心被分配的总量很大的(及未通过引用计数销毁的那些)对象。在这种情况下,解释器会暂停下来,试图...

python的回收机制是什么?

Python中的垃圾回收机制总体上有三种,引用计数 Python语言默认采用的垃圾收集机制是『引用计数法 Reference Counting』,该算法最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用,『引用计数法』的原理是:每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数...

python怎么进行内存管理的?

Python使用垃圾回收机制来管理内存。垃圾回收机制是一种自动化的内存管理技术,它可以自动识别和回收不再使用的内存。Python中的垃圾回收机制有两种方式:引用计数和循环垃圾收集。引用计数是Python中最简单、最基本的内存管理机制。它的原理是通过计数来管理内存。当对象被创建时,Python会为该对象创建一个引用...

python的内存管理机制是什么

这里以Python语言为例子,说明一门动态类型的、面向对象的语言的内存管理方式。一句话概括:引用计数为主,清除标记,分代回收为辅(推荐学习:Python视频教程)python的垃圾回收(3种)引用计数当对象的引用的计数器变为0的时候,该对象可能在内存中,但是已经不能访问。python的垃圾回收时候不能做其他操作...

python中的变量与垃圾回收

python中垃圾回收的算法回收的算法是采用引用计数,当程序中有一个变量引用该python对象时,python会自动保证该对象引用计数为1;当程序中有两个变量引用该python对象时,python会自动保证该对象计数器为2, 以此类推,当一个对象的引用计数器变为0 时,则说明程序中不再有变量对其进行引用,因此python就会...

Python是怎么进行内存管理的?

Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。1.Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。2.Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。3. 对于...

为什么说Python采用的是基于值的内存管理模式

先从较浅的层面来说,Python的内存管理机制可以从三个方面来讲 (1)垃圾回收 (2)引用计数 (3)内存池机制 一、垃圾回收:python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是 在运行时确定的。这也是为什么我们称Python语言...

BAT面试题28:Python是如何进行内存管理的

Python的内存管理,一般从以下三个方面来说:1)对象的引用计数机制(四增五减)2)垃圾回收机制(手动自动,分代回收)3)内存池机制(大m小p)1)对象的引用计数机制 要保持追踪内存中的对象,Python使用了引用计数这一简单的技术。sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用...

python的运行原理是什么?

6. 垃圾回收:Python中采用自动垃圾回收机制,当一个对象不再被引用时,垃圾回收器会自动回收其占用的内存空间。总的来说,Python的运行原理是将源代码经过词法分析、语法分析、语义分析、中间代码生成等步骤转化为可执行的中间代码,然后通过解释执行或编译执行方式运行程序。

Python如何进行内存管理

Python是如何进行内存管理的?答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制。一、对象的引用计数机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。引用计数增加的情况:1,一个对象分配一个新名称 2,将其放入一个容器中(如列表、元组或字典)引用...

Top