Python 内存管理 & 垃圾回收

非人话定义

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

Python 垃圾回收

引用计数器

1. 环状的双向链表 refchain , 循环双向链表

python 创建的任何对象都会放在refchain链表中。

1
2
3
4
5
6
7
8
9
10
name="gsz"
age=27
hobby =["游泳"]

name="gsz" 内部会创建一些数据【上一个对象、下一个对象、类型、引用的个数】
new= name 这个时候new会直接应用,并且引用个数+1

age=27 内部会创建一些数据【上一个对象、下一个对象、类型、引用的个数、value=18

hobby =["游泳"] 内部会创建一些数据【上一个对象、下一个对象、类型、引用的个数、items=元素、元素个数】

在C源码中,通过PyObject结构体(4个值)体现每个对象都有相同的值

多个元素组成的对象,PyObject+ob_size

2. 类型封装结构

1
2
3
4
5
6
7
8
data=3.14

内部会创建:
_ob_pre = refchain中的上一个对象
_ob_next = refchain中的下一个对象
ob_refcnt=1
ob_type=float
ob_fval = 3.13

3. 引用计数器

1
2
3
v1 =3.14
v2=999
v3=(1,2,3)

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

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

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

1
2
3
4
5
6
7
8
# 引用
a=999
b=a # 计数器+1
# 删除引用
a=999
b=a # a和b指向同一个对象
del b # b变量删除,b对应对象引用计数器-1
del a # a变量删除,a对应对象引用计数器-1

当一个对象的引用计数器为0时,意味着没有人在使用这个对象了,这个对象就是垃圾,然后进行垃圾回收。
回收:1.将对象从reflchain链表移除;2.将对象销毁,内存归还。

4. 循环引用存在问题

1
2
3
4
5
6
v1 = [1,2,3]
v2 = [4,5,6]
v1.append(v2) # 引用计数器+1=2
v2.append(v1) # 引用计数器+1=2
del v1 # 引用计数器-1
del v2 # 引用计数器-1

此时,变量v1,v2删除,但是指向的数据对象并没有销毁,这些数据一直存在内存中

标记清除

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

实现方法: 在python的底层再维护一个链表,链表中专门放那些可能存在循环引用的对象(list,tuple,dict,set等可能被引用的数据)

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

也存在问题:

  • 什么时候扫描?
  • 链表扫描代价大,耗时久

分代回收

  1. 将可能存在循环引用的对象维护成三个链表:
    • 0代: 0代中对象个数达到700个扫描一次
    • 1代: 0代扫描10次,1代扫描一次
    • 2代: 1代扫描10次,2代扫描一次

      总结

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

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

在源码内部当达到各自的阈值,就会触发扫描链表进行标记清除的东走(有循环则各自-1)

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

缓存机制

1. 池

为了避免重复创建和销毁常见对象,维护池。

1
2
3
4
# 启动解释器时,python内部会帮助创建-5,-4,...257
v1 = 7 # 内部不会开辟,直接去池中获取
v2 = 9 # 内部不会开辟,直接去池中获取
v3 = 9 # 内部不会开辟,直接去池中获取

2. free_list(float/list/tuple/dict)

当一个对象的引用计数器为0时,按理说应该回收,但内部不会直接回收,而是将对象添加到free_list链表中当缓存,以后再创建对象时,不会重新开辟内存,而是直接使用free_list

1
2
3
v1 = 3.14 # 开辟内存,内部存储结构体中定义的那几个值,并存到refchain中
del v1 # refchain中移除,将对象添加到free_list中,当free_list满了则销毁
v9 =99.99 # 不会重新开辟内存,去free_list中获取对象,对象内部数据初始化,再放到refchain中

参考:https://pythonav.com/wiki/detail/6/88/