python学习(5)之垃圾管理

2.3 内存管理和垃圾回收

在python中说到内存管理和垃圾回收,经典的一句话就是:引用计数器为主,标记清除和分代回收为辅+缓存机制。

2.3.1 引用计数器

在python中程序所创建的对象都会存贮在一个refchain(环状双向链表)中,是创建的任何数据对象实例都存储其中。在底层代码C语言的实现中,我们可以认为在创建对象的时候在内存空间中创建了一个结构体,为了实现环状双向链表的功能,所以其结构体构成需要有相同的元素,主要的四个相同的元素为:上一个对象的指针,下一个对象的指针,该对象的数据类型,引用个数(变量对该结构体的内存的映射个数)。当然不同的数据类型所存储的结构体的不同点,就是数据值的不同等等。

可以看一下C语言源码的实现:

#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;

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; //元素个数
}PyVarObject;

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

不同点:有多个元素组成的对象(如列表,字典等):PyObject结构体(4个值) + ob_size(元素个数)

// float类型的结构体定义方法
typedef struct{
    PyObject_HEAD
    double ob_fval;
}PyFloatObject;

// list类型的结构体定义方式
typedef struct{
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
}PyListObject;

如同上述举例看出:float类型的对象使用的是PyObject结构体,再加上一个ob_fval用来保存具体的数值。而list类型使用的是PyObject_VAR_HEAD结构体,定义其变量的初始状态。ob_item是指向PyObject类型的指针的指针。首先说明一下,list是一个容器,里面可以容纳很多PyObject类型的对象,ob_item指向一块连续空间,这块空间装载了n个指针,每一个指针指向一个PyObject对象。allocated表明当前为ob_item分配了多少空间。由此可以看出list是一个顺序表。

综上所述,当python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段来进行创建相关的数据,然后将对象添加到refchain双线链表中。每个对象中有 ob_refcnt就是引用计数器,值默认为 1 ,当有其他变量引用对象时,引用计数器就会发生变化。

a = 3.14 # 创建了一个float对象 其计数引用器默认为1
b = a # 现在b引用了该对象,该对象的引用计数器为2
del b # 现在删除了b变量,那么其计数引用器默认为1
del a #  现在删除了a变量,那么其计数引用器默认为0 ,所以释放内存空间

当一个对象的引用计数器为0时,意味着没有人再使用这个对象了,这个对象就是垃圾,垃圾回收。其所进行的操作是:

  • 1.对象从refchain链表移除;

  • 2.(可以简单认为)将对象销毁,内存归还。

如果只使用引用计数器方法,那就会出现一个大问题,如果变量之间互相引用,形成循环引用,导致交叉感染,如果在把变量全部清除掉的话, 由于引用计数器不为0,所以内存空间不会释放,也就是说没有变量去应用该对象的内存空间,也就是形成了脏数据。

v1 = [1, 2, 3]  # refchain中创建一个列表对象,由于v1=对象,所以列表对象的应用计数器为1
v2 = [4, 5, 6]  # refchain中创建一个列表对象,由于v2=对象,所以列表对象的应用计数器为1
v1.append(v2)  # 把v2追加到v1中去,因为引用了v2的对象所以该对象的引用计数器加1,最终为2
v2.append(v1)  # 把v1追加到v2中去,因为引用了v1的对象所以该对象的引用计数器加1,最终为2
del v1  # 引用计数器-1
del v2  # 引用计数器-1
# 消除了所有变量,但是由于两个对象的应用计数器没有为0,所以对象不能被销毁,内存空间没有变量引用,所以形成脏数据
2.3.2 标记清除

为了解决由于引用计数器的不足,从而引起的循环引用的问题,使用了标记清除这个概念。

实现:在python的底层再维护一个链表,链表中专门放那些可能存在循环引用的对象(list/tuple/dict/set)。基于两个链表进行维护,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

但是标记清除技术的引用会带来两个问题,一个问题是什么时候需要对可能存在循环引用的链表进行扫描;还有一个问题是,由于扫描循环引用列表需要对每一个元素都进行扫描,所以每次扫描的代价很大,所需要的持续时间很久。

2.3.3 分代回收

为了解决上述标记清除提出的两个问题,python又引用了分代回收机制,简单来说就是把原来的一个可能存在循环应用的链表,拆成了三个链表,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

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

也就是说,首先把列表等可能引起循环引用的对象元素全部放入在0代链表中,只有当0代链表中的对象个数达到700个,那么就会对0代中所有的元素进行扫描,如果出现循环引用那就使该对象的引用计数器减1,如果引用计数器为0,那就进行垃圾回收。然后把0代中剩余的对象元素放入一代中,并且清除0代中的对象元素,只有当0代这种操作执行10次,那么1代链表中的元素再一次进行扫描,只有当1代中的元素,扫描了10次,那么就会把1代中的对象元素升入2代中,并且清除1代中的对象元素,以此往复。

2.3.4 缓存机制

在python中对上述的流程又提出了优化机制,所以引入缓存机制。在python中缓存可以分为两个部分:池和free_list

池:通常是int和字符串使用

python中为为了避免重复创建和销毁一些常见对象,就创建了维护池(内部创建好了一些对象,需要变量引用的时候就直接去拿,不需要重复的创建和销毁)。

# 启动解释器时,Python内部帮我们创建:-5、-4、..... 257
v1 = 7  # 内部不会开辟内存,直接去池中获取
v2 = 9  # 内部不会开辟内存,直接去池中获取
v3 = 9  # 内部不会开辟内存,直接去池中获取
print(id(v2),id(v3)) # 会发现这两个内存地址是相同的

free_list:通常是float/list/tuple/dict等使用

当一个对象的引用计数器为0时,按理说应该回收,但是python为了优化,内部不会直接回收,而是将对象添加到 free_list 链表中当缓存。以后再去创建对象时,不再重新开辟内存,而是直接使用free_list。

v1 = 3.14  # 开辟内存,内部存储结构体中定义那几个值,并存到refchain中。
del v1     # refchain中移除,将对象添加到 free_list 中,如果free_list满了则销毁。
v9 = 999.99 # 不会重新开辟内存,去free_list中获取对象,对象内部数据初始化,再放到refchain中。
2.3.5 C语言的底层源码实现

以float类型举例如下:

// float类型的结构体定义方法
typedef struct{
    PyObject_HEAD
    double ob_fval;
}PyFloatObject;

创建float对象时,需要运行python语句:

val = 3.14

当按照上述方式创建一个Float类型对象时,python源码内部会先后执行如下代码

/* Special free list
   free_list is a singly-linked list of available PyFloatObjects, linked
   via abuse of their ob_type members.
*/
static PyFloatObject *free_list = NULL;
static int numfree = 0;
  
PyObject *
PyFloat_FromDouble(double fval)
{
    PyFloatObject *op = free_list;
    if (op != NULL) {
        free_list = (PyFloatObject *) Py_TYPE(op);
        numfree--;
    } else {   
        // 第一步:根据float类型大小,为float对象开辟内存。
        op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
        if (!op)
            return PyErr_NoMemory();
    }
    // 第二步:在为float对象开辟的内存中进行初始化。
    /* Inline PyObject_New */
    (void)PyObject_INIT(op, &PyFloat_Type);    
    // 第三步:将值赋值到float对象开辟的内存中。
    op->ob_fval = fval;
    // 第四步:返回已经创建的float对象的内存地址(引用/指针)
    return (PyObject *) op;
} 

第一步:根据float类型所需的内存大小,为其开辟内存

static PyMemAllocatorEx _PyObject = {
#ifdef PYMALLOC_DEBUG
    &_PyMem_Debug.obj, PYDBG_FUNCS
#else
    NULL, PYOBJ_FUNCS
#endif
    };

void *PyObject_Malloc(size_t size)
{
    /* see PyMem_RawMalloc() */
    if (size > (size_t)PY_SSIZE_T_MAX)
        return NULL;

    // 开辟内存
    return _PyObject.malloc(_PyObject.ctx, size);
}

第二步:对新开辟的内存中进行类型和引用的初始化

#define PyObject_INIT(op, typeobj) \
    ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )

static PyObject refchain = {&refchain, &refchain};

void _Py_AddToAllObjects(PyObject *op, int force)
{

    if (force || op->_ob_prev == NULL) {
        op->_ob_next = refchain._ob_next;
        op->_ob_prev = &refchain;
        refchain._ob_next->_ob_prev = op;
        refchain._ob_next = op;
    }
}
void _Py_NewReference(PyObject *op)
{
    _Py_INC_REFTOTAL;
    // 对新开辟的内存中的的引用计数器初始化为1。
    op->ob_refcnt = 1;
    // 将新开辟的内存的指针添加到一个双向链表refchain中。
    _Py_AddToAllObjects(op, 1);
    _Py_INC_TPALLOCS(op);
}

float对象引用时,在python代码中

val = 7.8
data = val

在源码中实现:

//在给对象创建新引用时,会对其引用计数器+1的动作
#define Py_INCREF(op) (                         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
    ((PyObject *)(op))->ob_refcnt++)

销毁对象时,python代码

val = 3.14
# 主动删除对象
del val

在源码中实现:

#define Py_DECREF(op)                                   \
    do {                                                \
        PyObject *_py_decref_tmp = (PyObject *)(op);    \
        if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
        --(_py_decref_tmp)->ob_refcnt != 0)             \
            _Py_CHECK_REFCNT(_py_decref_tmp)            \
        else                                            \
        _Py_Dealloc(_py_decref_tmp);                    \
    } while (0)
void
_Py_Dealloc(PyObject *op)
{
    // 第一步:调用float类型的tp_dealloc,进行内存的销毁
    destructor dealloc = Py_TYPE(op)->tp_dealloc;
    // 第二步:在refchain双向链表中移除
    _Py_ForgetReference(op);
    (*dealloc)(op);
}
Objects/object.c

第一步,调用float类型的tp_dealloc进行内存的销毁。

按理此过程说应该直接将对象内存销毁,但float内部有缓存机制,所以他的执行流程是这样的:

  • float内部缓存的内存个数已经大于等于100,那么在执行del val的语句时,内存中就会直接删除此对象。

  • 未达到100时,那么执行 del val语句,不会真的在内存中销毁对象,而是将对象放到一个free_list的单链表中,以便以后的对象使用。

    #ifndef PyFloat_MAXFREELIST
    #define PyFloat_MAXFREELIST    100
    #endif
    static int numfree = 0;
    static PyFloatObject *free_list = NULL;
    
    
    
    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 */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        ...
    };
    
    static void float_dealloc(PyFloatObject *op)
    {
        // 检测是否是float类型
        if (PyFloat_CheckExact(op)) {
    
            // 检测缓冲池个数是否大于100个
            if (numfree >= PyFloat_MAXFREELIST)  {
                // 如果大于100个,则在内存中销毁对象
                PyObject_FREE(op);
                return;
            }
            // 否则,缓冲池个数+1
            // 并将要销毁的数据加入到free_list的单项链表中,以便以后创建float类型使用。
            numfree++;
            Py_TYPE(op) = (struct _typeobject *)free_list;
            free_list = op;
        }
        else
            Py_TYPE(op)->tp_free((PyObject *)op);
    }
    
       PyObject_FREE(op);
            return;
        }
        // 否则,缓冲池个数+1
        // 并将要销毁的数据加入到free_list的单项链表中,以便以后创建float类型使用。
        numfree++;
        Py_TYPE(op) = (struct _typeobject *)free_list;
        free_list = op;
    }
    else
        Py_TYPE(op)->tp_free((PyObject *)op);
    

    }

    
    
    

  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值