python内存管理与垃圾回收

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

提示:基本原理这句话基本覆盖python内存管理和垃圾回收的所有精髓。


一、基本流程

1、在python内部维护了一个双向环状链表,即refchain,引用计数器的值就是在此链表中体现。
2、这个引用次数的值是基于ob_refcnt来记录的,将其称为引用计数器。
3、引用计数器的值是随着对象的操作变化而变化的。例如创建了某变量,则ob_refcnt的值加1,变量释放时ob_refcnt的值减1.
4、 当ob_refcnt的值变为0时,
进行垃圾回收(-5~257范围内的int数值类型除外,其默认引用计数器的值为1。最终删除对象时,将引用计数器还原为1)。


二、引用计数器

python内部创建的任何一个对象都会放到refchain链表中,在其内部创建一些数据(上一个对象:_ob_prev, 下一个对象:_ob_next,  类型:ob_type,引用计数器:ob_refcnt,元素ob_fval  等)。
   1、 程序会根据数据类型的不同找到其相应的结构体,创建相关的数据,然后将其添加到refchain中。
   2、 python的C源码中,有两个关键的结构体:1)PyObject  2)   PyVarObject。一般的数据类型都是根据PyObject 创建的,但是有元素个数要求的都是根据
 PyVarObject创建的。
   3、需要注意,在python3.6版本中,int类型的数据其实也是根据
 PyVarObject结构体创建的。

   4、小栗子1:

a=999  # 引用计数器变为1
b=a  # 引用计数器的值变为2
del b  # 引用计数器值为1
del  a  # 引用计数器的值变为0,进行垃圾回收

   注:垃圾回收原理:1)从refchain中将此对象进行删除。2)内存归还

    5、小栗子2

v1=[11, 22, 33]  # 在refchain中创建一个列表对象,由于V1=对象,所以列表对象引用计数器为1.
v2=[44, 55, 66]   3 refchain中再创建一个列表对象,由于v2=对象,所以列表对象引用计数器为1.
v1.append(v2) # 把v2追加到v1中,则v2对应的对象引用计数器加1,最终为2.
v2.append(v1)   # 把v1追加到v2中,则v1对应的对象引用计数器加1,最终为2.

del v1  # ob_refcn的值t-1,则最终变为1
del  v2  # ob_refcnt的值-1,则最终变为1

      1) 出现的问题:本来已经删除了v1和v2,但是其引用计数器的值最终变为1,而不是0,导致不能进行垃圾回收。这就是循环引用问题,出现交叉感染。

       2) 解决措施:标记清除


三、标记清除

1、除了refchain之外,再程序底层再创建一个链表,存储可能出现循环引用问题的这些对象
2、重新创建的链表在
某种情况下(分代回收中解释是哪种情况)被触发。

3、重新创建的链表 通过扫描  可能存在循环引用的链表,检查是否存在循环引用的问题。如果有,则让双方的引用计数器同时-1.当引用计数器的值变为0时进行垃圾回收

4、问题:

  • 什么时候扫描可能存在循环引用的链表
  • 每次触发都要对链表进行扫描的话,其代价可能过大。

5、解决

  • 使用 分代回收


四、分代回收

将可能存在循环引用的对象维护成3个链表,即0代,1代,2代。

  • 0代链表:当可能存在循环引用的对象被创建时,都会被放入0代链表中。当其中的对象个数达到700时,扫描一次此链表。如果引用计数器的值为0,则其是垃圾,进行垃圾回收;如果不是垃圾,则将其添加到(升级到)1代链表中。总结为,0代中对象达到700则进行一次扫描。
  • 1代链表:1代在每次接收了来自0代的对象后,会标记其扫描次数。当所标记的次数达到10时,1代链表的数据会被扫描。如果引用计数器的值为0,则其是垃圾,进行垃圾回收;如果不是垃圾,则将其添加到(升级到)2代链表中。总结为,0代扫描10次,则1代扫描1次。
  • 2代链表:2代同一代,即1代共扫描10次后,2代扫描1次。

备注:在整个内存管理的过程中,python内部还会有一些优化机制(缓存与驻留机制),用于简化内存管理的操作。 


五、缓存与驻留机制

 (1)缓存机制

  1. 缓冲池(int)
  • 针对-5~257范围内的int类型数据
  • 启动解释器时,python内部自动创建-5~257范围内的值,其引用计数器的值默认为1.
  • 每次创建这些范围内的对象时,都会从缓冲池中去取。
  • 最终删除对象时,不会进行垃圾回收,而是将其引用计数器的值还原为1

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

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

  • float类型
    v1=3.14 # 开辟内存来存储float对象,并将其添加到refchain链表中
    del v1   # Ob_refcnt的值-1,如果为0则在refchain链表中移除,并将其添加到float类型的free_list中
    v2 = 9.999 # 优先去free_list 中获取对象,free_list为空时才重新开辟内存
    

    由于使用了缓存机制,则这里的v1和v2输出的地址值会是相同的 。float类型所维护的free_list最多可缓存100个float对象。

  • list类型

v1 = [000, 111, 222]
id_v1 = id(v1)  
del v1
v2 = [222, 111, 999]
print(id(v2)==id_v1)  # 则输出为True

list类型维护的free_list数组最多可缓存80个list对象。

  • dict类型

 

v1 = {'k1': 123}
id_v1 = id(v1)
del v1
v2={"name":111, "age":222}
print(id(v2)==id_v1)

输出为True,最多存储80个

  • tuple 

 free_list的结构为[0,1,2,3......19]:0代表当前位置可存储2000个空元组,1代表当前位置最多可存储2000个具有1个元素的元组;......19代表当前位置最多可存储具有19个元素的元组;

v1=(1, 2)
id_v1=id(v1)
v2=("aaaa", "2aaa")
print(id(v2)==id_v1)

 输出为True

(2)驻留机制

  • 维护unicode_latin1[256]链表,内部将所有的ascii字符缓存起来,以后使用时不再重复创建
  • 针对于长度不大于20,只含有字母、数字、下划线的字符串类型。如果内存中已经存在符合该要求的字符串,则不会重新创建,而是直接使用。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值