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

结构体PyObject和PyVarObject

在了解内存管理机制前先要知道俩个结构体

结构体1:

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

源码中用c语言编写的一个结构体PyObject,内部包含四个值风别是:链表中上一个值的地址、下一个值的地址、引用计数器、类型。

结构体2:

typedef struct {
    PyObject ob_base;   // 内部封装4个值
    Py_ssize_t ob_size; // 成员个数
} PyVarObject;

结构体PyVarObject中除了包含结构体1中的四个值外额外多了一个成员个数

float类型

#define PyObject_HEAD        PyObject ob_base;
typedef struct {
    PyObject_HEAD // 上一个、下一个、引用计数器、类型
    double ob_fval; // 1.1
} PyFloatObject;

在创建一个floatv1 = 1.1类型时会调用PyFloatObject保存四个值:链表中上一个值的地址、下一个值的地址、引用计数器为1,类型为float,除了这四个值外还会保存一个ob_fval,它其实就是1.1这个值

list类型

#define PyObject_VAR_HEAD      PyVarObject ob_base;

typedef struct {
    PyObject_VAR_HEAD  //5个值,上一个、下一个、引用计数器、类型、成员个数
    PyObject **ob_item; // 列表中的每个元素内存地址
} PyListObject;

在创建list类型时,调用pyvarobject,在内部保存5个值,处此之外ob_item还保存了列表中每个元素的内存地址,这也是为什么列表中存的不是值本身而是内存地址的原因。

通过list和float类型得出一个结论:在Python中由单个元素组成的对象,他的内部是包含PyObject结构体创建对象。 有多个元素组成的对象,他的内部是包含PyVarObject结构体创建的对象。

单个元素组成的对象有哪些?在python中其实只有float是由单个元素组成,其他类型都是由多个元素组成

内存管理机制

创建变量:

# 第一步:开辟内存,并做初始化:上一个、下一个、引用计数器=1、类型=str、成员个数=3
# 第二步:将对象加入到双向链表中
v1 = "abc"

# 第一步:开辟内存,并做初始化:上一个、下一个、引用计数器=1、类型=list、成员个数=3
# 第二步:将对象加入到双向链表中
v2 = [11,22,33]

# 第一步:开辟内存,并做初始化:上一个、下一个、引用计数器=1、类型=float
# 第二步:将对象加入到双向链表中
v3 = 9.9

# v1指向的内存中 引用计数器+1
v4 = v1

# v1指向的内存中 引用计数器+1
temp = []
temp.append(v1)

# v4指向的那个内存中 引用计数器-1
del v4

# v4指向的那个内存中 引用计数器-1
temp.remove(v1)

# v4指向的那个内存中 引用计数器先 +1 再 -1
def func(arg):
    pass
func(v1)

当引用计数器为0时,则将内存销毁并在双向链表中移除。

python针对float和list类型做了优化处理:当list或float类型销毁时其实它不会真正的销毁,只会在双向链表中移除,并存放到另一个链表free_list中,当在创建此类型对象时不会再重新开辟内存地址,而是去free_list中查找,如果存在就使用。

Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> v1 = 1.1
>>> print(id(v1))
1361327366504
>>> del v1
>>> v2 = 1.1
>>> print(id(v2))
1361327366504
>>>
>>>> l1 = [1,2,3]
>>> print(id(l1))
1361359174472
>>> del l1
>>> l2 = [1,2,3]
>>> print(id(l2))
1361359174472
>>>

这个列表也不是无限的,在源码中可以看到list类型上限是80个,float类型上限是100个

#define PyList_MAXFREELIST 80
#define PyFloat_MAXFREELIST    100

垃圾回收机制

python的垃圾回收机制可以用一句话概括以引用计数器为主、标记清除和分代回收为辅。 引用计数器就是上面pyobject和中的ob_refcnt,但是仅靠引用计数器会导致一个bug无法解决:当两个元素相互引用时,引用计数器永远不会变为0。所以产生了标记清除这个概念。

标记清除:
python在做内存管理时其实一共维护了两个链表,第一个链表存储不会出现相互引用的数据类型,比如str、int、bool等。而另一个链表用来存储可能会出现相互引用的数据类新,比如list、dict、set、tuple、对象。针对此链表Python内部会定期进行检查,如果存在循环引用(检查链表时发现引用了之前已经检查过的元素),则让双方引用计数器均-1,当计数器为0时,则认为是垃圾,就进行清除和销毁。

# 相互引用示例
l1 = [1,2,3]
l2 = [4,5,6]
l1.append(l2)
l2.append(l1)
del l1
del l2

分代回收:

分代回收其实是python做的一种优化机制,为减少扫描元素个数和次数,将常驻内存中的元素可以升级,python内部总共维护了3个链表,称为:0代、1代、2代,当多次扫描0代链表时发现某个元素都不是垃圾的时候,就会将这个元素放到1代链表中,当多次扫描1代链表发现某个元素常驻时,则将这个元素放到2代链表中。默认情况下0代链表扫描10次1代链表扫描1次,1代链表扫描10次2代链表扫描1次

发布了83 篇原创文章 · 获赞 4 · 访问量 4044

猜你喜欢

转载自blog.csdn.net/wy121221612/article/details/104391995