一、引用计数器
二、标记清除
三、分代回收

记住一句话:

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

一、引用计数器

  1. 说到引用计数器,我们先说一个对象,叫环状双向链表(refchain)

在python中创建任何一个对象都会加入到refchain中,例如:name = ‘张三’,实际上系统会将name这个变量打包到一个结构体中,接着再定义两个指针,一个指向前驱节点,一个指向后继节点,类型,引用个数。
基本结构:[前驱节点指针,后继节点指针,类型,引用个数]
当我们再运行语句new = name的时候,系统并不会再次创建一个对象,而是将new也指向之前创建好的对象,接着再把引用个数改成2,表示当前有两个引用指向该对象。

接下来我们来看看底层代码,这里是C语言的代码,我们只需要关注9到13行

python recv 缓存区 清空 python缓存重用机制_python recv 缓存区 清空

  1. 那么不同类型封装了哪些元素?
data = 3.14  #这句代码的执行实际上在底层发生了什么呢?

python recv 缓存区 清空 python缓存重用机制_python recv 缓存区 清空_02

  • 可以看到右上角的那个结构体就是存放flaot类型节点的,除了刚刚出现的那四个值以外,还有一个double类型的元素,这个元素就存放的是3.14
  1. 基于以上知识,我们现在可以来了解一下引用计数器
v1 = 3.14
v2 = 999
v3 = (1,2,3)
# 当程序运行时,会根据不同的类型找到对应的结构体,根据结构
# 体的字段来创建相关数据,接着再将对象添加到环装双向链表(refchain)
  • 在C源码中共有两个关键的结构体:PyObject,PyVarObject

PyObject:存上一个节点,下一个节点等所有节点公共的值
PyVarObject:当有多个元素时(例如,字符串),存公共元素

  • 看上面的源码,每个结构体中都有一个ob_refcnt,这个就是引用计数器,它也存在于每一个对象,当有其它变量引用这个对象时,引用计数器就会发生变化
  • 引用
a = 99999
b = a
# 那么这个时候,99999的引用计数器的值就是2了
  • 删除引用
a = 99999
b = a
del b # 删除b引用,b对应的引用计数器-1
del a # 删除a引用,a对应的引用计数器-1

# 当引用计数器为0的时候,意味着没有引用再使用这个对象了
# 这个对象就成为了垃圾,这个时候就会触发垃圾回收机制,
# python程序会先把refchain中的该对象删除,之后再释放这个对象
# 将这个对象所在的内存返还给系统

二、标记清除

  1. 引用计数器的功能看似完美,但是也存在一个问题,那就是循环引用的问题。
  • 我们上面已经删掉了v1,v2,照常理来说[11,22,33],[44,55,66]这两个对象都没有变量在引用了,但是它们的引用计数器的值并没有变成0,所以不会被当成垃圾回收

为了解决这个问题,python中引入了标记清除

实现方式:python在底层再维护一个链表,这个链表专门用来存放,可能出现循环引用问题的对象(list/tuple/dict/set)

python recv 缓存区 清空 python缓存重用机制_链表_03


python内部在某种情况下会触发,会去扫描可能发生循环引用的链表,挨个检查这些对象是否发生了循环引用,若发生了,则双方引用计数器-1

  • 但是每次扫描循环引用的链表代价大,耗时久,所以python中又引入了分代回收

三、分代回收

python recv 缓存区 清空 python缓存重用机制_python_04

  • 将可能存在循环引用的链表分为三个链表,每一个链表都是一个环状双向循环链表
  1. 0代:对象达到700个,扫描一次
  2. 1代:0代扫描10次,1代扫描1次
  3. 2代:1代扫描10次,2代扫描1次

总结:

python recv 缓存区 清空 python缓存重用机制_python_05