垃圾回收与缓存机制
垃圾回收
引用计数器为主,分代回收和标记清除为辅
引用计数器
python中创建的任何对象都会放在refchain的双向列表中,如
list = ['11','22','sda']#列表对象
a = 18#整形对象
这些对象都会放到双向列表中,也就是说得到了refchain,也就得到了python中所有的对象
当创建一个对象的时候,对象的内部都会有这样的一些属性:
- 指向上一个对象的指针
- 指向下一个对象的指针
- 类型
- 引用的个数
#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;
源码中有两个重要的struct:PyObject,PyVarObject
PyObject:指向上/下一个对象的指针,类型,引用计数器
PyVarObject:存储由多个元素组成的类型的值,像字符串
何为引用个数:
当我们创建一个对象,如a = 'using'
,会给using开辟一个内存空间存放到双向列表里,如果我们这时b = a
,会把b指向using,而不是重新弄一个新的对象,这时using的引用个数变成了2
何时计数增加:
- a = 2对象被创建
- b = a对象被引用
- func(a)对象被作为参数传到函数中
- list = [1,2,'s',a]对象被作为元素存储在容器中
何时计数减少:
- 当该对象的别名被显示销毁时 del a
- 当对象的别名被赋予新值 a = 26
- 当对象离开作用域,如当func函数执行完毕,函数里面的局部变量引用计数器就会减一(全局变量不会)
- 当元素从容器中删除或者容器被销毁
当计数为0,将进行垃圾回收
循环引用的问题:
a1 = [1,2,3]
b1 = [2,3,4]
a1.append(b1)#b1对应的[2,3,4]的对象引用为2
b1.append(a1)#a1对应的对象引用为2
del a1#引用计数器减一
del b1
#a1,b1计数器都为1
这样虽然a1,b1被我们销毁了,但是对应的对象依旧存在内存中没有被回收
评价
优点:简单,没有引用就会被释放,实时性
缺点:循环引用,维护计数消耗的资源
标记清除
原理:
再去维护一个链表,这个链表专门用于存放那些可以循环引用的对象,python会在一些情况下去扫描这个链表里面的每个元素,如果检查到循环引用,则让双方的引用计数器都各自减一,如果是0则垃圾回收
标记清除算法:
对象之间通过指针连在一起构成一个有向图,从根节点出发,腌着有向边遍历对象,可达的对象标记为活动对象,不可达的就是要被清除的非活动对象。跟对象就是全局变量、调用栈、寄存器。如图,从黑点出发,123可达,那就会标记为活动对象,44不可达,会被回收
问题:
什么时候去扫描?
标记和清除的过程效率并不高,清除非活动的对象千必须顺序扫描整个堆内存,即扫描所有的对象
分代回收
原理:设置三个列表
0代:0代中对象个数打到700个,扫描一次
1代:0代扫描10次,则1代扫描1次
2代:1代扫描10次,则2代扫描1次
当0代中个数达到700时,会对0代中所有元素进行一次扫描,如果是循环引用则计数器-1,然后判断是否为0进行回收。如果没有回收,则从0代升级到1代,这时候0代是空,1代会记录0代依旧扫描1次
弱代价说:年轻的对象通常死的快,而老对象可能存活时间更长
缓存机制
缓存在python中分为两大类
池
python中为了避免重复创建和销毁而维持的一些常见对象,如-5到256,bool,一些字符或字符串,这些都是常用值,会在内存中先把这些值都创建好。可以用id()函数来验证
因为代码块的缓存机制,交互模式和命令模式下测试的结果可能不同。一个模块、函数、类、文件等都是一个代码块。而在交互模式下,一行就是一个代码块
同一个代码块中的缓存机制:
python在执行同一个代码块的初始化对象的命令时,会检查这个是否已经存在,如果是,则引用,如果不是,则开辟新的内存
适用对象:int(float),str,bool
从同一块代码块中因为驻留机制,对于上面那些数据类型,只要对象相同,内存地址就共享,从不同代码块中只有引用对象为池中常见对象,才会有内存共享
free_list
python为了内部优化,当一个对象的引用计数为0时,不会立即回收,而是添加到free_list链表中当作缓存,以后创建对象就不再重新开辟内存,而是使用free_list,但是free_list容量有限,默认数量为80,当满了的时候会去直接销毁,代表性 的有float,list,tuple,dict
评论已关闭