Python垃圾回收机制

python垃圾回收机制和内存管理机制

引用计数为主, 标记清除和分代回收为辅+缓存机制

1.引用计数器

1.1 环状的双线链表 refchain

​ 在python程序中创建的任何对象都会存在refchain双向链表中

    name = 'ds'
    age = 18
 	hobby = ['篮球','美女']
# str 
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数]
# name = 'ds'
new = name   new 不会重新创建一个ds的数据, 是指向ds这个数据,引用计数则加+1


# float 多一个val , 存储数据具体值
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数,val=18]
age = 18 

# list , 多存一个items存放的元素容器 和元素个数
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数,items=元素,元素个数]
hobby = ['篮球','美女']


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

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

# C 源码. 
PyObject_VAR_HEAD
// 宏定义 , 包含上一个,下一个,用户构建双向链表,(放到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;
1.2 类型封装的结构
  • float 类型

    typedef struct {
        PyObject_HEAD
        double ob_fval;   // 存放 具体数据
    } PyFloatObject;
    
    // 举例子
    data = 3.14
        _ob_next  : refchain 双向链表中的上一个对象
        _ob_prev  : refchain 双线链表中的下一个对象
        ob_refcnt : 1  // 引用计数
        ob_type	  : float  //类型
        ob_fval   : 3.14
    
  • int 类型

    struct _longobject {
    	PyObject_VAR_HEAD
    	digit ob_digit[1]; 
    };
    
  • list 类型

    typedef struct {
        PyObject_VAR_HEAD
        /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
        PyObject **ob_item;   // 元素列表
    
        /* ob_item contains space for 'allocated' elements.  The number
         * currently in use is ob_size.
         * Invariants:
         *     0 <= ob_size <= allocated
         *     len(list) == ob_size
         *     ob_item == NULL implies ob_size == allocated == 0
         * list.sort() temporarily sets allocated to -1 to detect mutations.
         *
         * Items must normally not be NULL, except during construction when
         * the list is not yet visible outside the function that builds it.
         */
        Py_ssize_t allocated;  // 元素个数
    } PyListObject;
    
  • tuple 类型

    typedef struct {
        PyObject_VAR_HEAD
        PyObject *ob_item[1];
    
        /* ob_item contains space for 'ob_size' elements.
         * Items must normally not be NULL, except during construction when
         * the tuple is not yet visible outside the function that builds it.
         */
    } PyTupleObject;
    
  • dict 类型

    typedef struct {
        PyObject_HEAD
    
        /* Number of items in the dictionary */
        Py_ssize_t ma_used;
    
        /* Dictionary version: globally unique, value change each time
           the dictionary is modified */
        uint64_t ma_version_tag;
    
        PyDictKeysObject *ma_keys;
    
        /* If ma_values is NULL, the table is "combined": keys and values
           are stored in ma_keys.
    
           If ma_values is not NULL, the table is splitted:
           keys are stored in ma_keys and values are stored in ma_values */
        PyObject **ma_values;
    } PyDictObject;
    
1.3 引用计数器
v1 = 3.14
v2 = 1
v3 = (1,2,3)

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

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

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

  • 引用

    a = 999
    b = a
    
    # 999 对象 的引用计数器就会 +1
    
  • 删除

    a = 999
    b = a
    
    del b   # 1. b变量删除, 2. b对应的对象的引用计数器 -1 
    del a   # 1. a变量删除, 2. a对应的对象的引用计数器 -1 , 就是 0 
    
    # 当一个对象的引用计数器 为 0 时, 意味着没有再使用这个变量, 这个变量就是垃圾. 垃圾就要进行垃圾回收机制
    
    # 垃圾回收,两件事 :
    	1. 对象从refchain双线链表中移除
    	2. 将对象销毁,内存归还.
    
1.4 循环引用问题
v1 = [1,2,3]
v2 = [4,5,6]
v1.append(v2)    // v2 追加到v1列表中, v2的引用计数器+1,最终为2
v2.append(v1)	// v1 追加到v2列表中, v1的引用计数器+1,最终为2


del v1   // 删除v1 变量,引用计数器-1
del v2  // 删除v2 变量,引用计数器-1

# 问题: 
	此时, v1和v2的引用计数器还是1,不是0,所以不会被当做垃圾给回收?
    `标记清除` : 解决这样的问题

2.标记清除

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

实现:在python的底层,在维护一个链表,链表中专门存放那些可能存在循环引用的对象(list/set/tuple)

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

3.分代回收

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

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

4.小结

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

但是,在python中对于那些可以有多个元素组成的对象,可能会存在循环引用的问题,为了解决这个问题python又引用了标记清除分代回收

,在python内部维护了4个链表:

  • refchain
  • 2代 10次
  • 1代 10次
  • 0代 700个对象

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

但是:源码内部在上述的流程中提出了优化机制缓存

5.python缓存

5.1 小数据池(int , str字符串)

​ 为了避免重复的创建和销毁一些常见对象,维护了一个池.

v1 = 7
v2 = 9
v3 = 9

# python 为了节省资源
    1. 在启动解释器的时候, 内部会创建`-5,-4,....256`范围数值
    2. 在内部 v1 =7 不会创建新的新对象,也不会开辟内存.直接上池中去取数据

    # print(id(v2),id(v3))

	3.超过 -5 ... 256 这个范围,重新开辟内存,创建对象 
5.2 free_list(float:100/tuple:20/dict:80/list:80)

​ 当引用计数器为0时,按理说是要进行垃圾回收. python内部不会去回收,而是将对象添加到free_list这个链表中作为缓存. 以后再去创建对象时,不会再去开辟内存,直接使用free_list中的数据

v1 = 3.14  # 1. 开辟内存,创建对象,引用计数器为1,并添加到refchain链表中

del v1     # 2. 从refchain中移除 , 将这个对象添加到free_list中,如果free_list链表缓存满了,再进行垃圾回收

v9 = 999.99 # 3. 不会重新开辟内存,直接去free_list中获取一个float类型的数据,对象内部数据初始化,再放到refchain链表中

银角大王笔记:https://pythonav.com/wiki/detail/6/88/
原文地址:https://www.cnblogs.com/dengz/p/14653788.html