垃圾回收

引用计数器为主,分代回收和标记清除为辅

引用计数器

python中创建的任何对象都会放在refchain的双向列表中,如

list = ['11','22','sda']#列表对象
a = 18#整形对象

这些对象都会放到双向列表中,也就是说得到了refchain,也就得到了python中所有的对象

当创建一个对象的时候,对象的内部都会有这样的一些属性:

  1. 指向上一个对象的指针
  2. 指向下一个对象的指针
  3. 类型
  4. 引用的个数
#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

何时计数增加

  1. a = 2对象被创建
  2. b = a对象被引用
  3. func(a)对象被作为参数传到函数中
  4. list = [1,2,'s',a]对象被作为元素存储在容器中

何时计数减少:

  1. 当该对象的别名被显示销毁时 del a
  2. 当对象的别名被赋予新值 a = 26
  3. 当对象离开作用域,如当func函数执行完毕,函数里面的局部变量引用计数器就会减一(全局变量不会)
  4. 当元素从容器中删除或者容器被销毁

当计数为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不可达,会被回收

avatar

问题:
什么时候去扫描?

标记和清除的过程效率并不高,清除非活动的对象千必须顺序扫描整个堆内存,即扫描所有的对象

分代回收

原理:设置三个列表

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

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

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

avatar

当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

标签: none

评论已关闭