Python内存管理和垃圾回收

垃圾回收&&内存管理
引用计数器

Python它的内部有一个ref_chain环状双向列表,我们的对象都存在这个环状双向列表里面,这个环状双向链表主要保存

  • 上一个对象和下一个对象
  • 对象的类型,例如字符形等等
  • 引用计数器,ob_refcnt
  • 这个对象的值
  • 如果对象是列表,元组等,会保存他们的元素,以及个数‘

环状双向链表保存的计数器,当我们使用del删除一个对象的时候,它的值就减一,如果计数器是0了,就会被回收。当我们多次引用一个变量的时候,它的值会加一,而不是重复的创建。

但是如果单单只靠这个链表保存的计数器ob_refcnt还不行,因为多个元素组成的对象,比如列表,元组可能会有循环引用的问题。

比如我有A,B两个列表,他们刚开始引用计数器都是1,如果我操作,A.append(B),那么B的引用计数器会加一,也就是变成2,然后如果在操作del B,那么B理应被清除,因为它没有对象引用,但是他的引用计数器这时候是1,不是0,所以不会被回收。

当这种循环引用的情况越来越多的时候,就会导致我们内存越来越慢,最后可能重启才能恢复。

标记清除

为了解决这问题,Python有标记清除和分代回收机制。在Python底层还有一种链表,它专门存那些可能会引起循环引用的对象,比如列表,元组。Python会去扫描这个链表,看看这里面的对象有没有循环引用,如果有,就把计数器减一.

这个扫描可能很耗时,怎么扫描呢?

分代回收

Python有分代回收机制

分代回收机制有3个链表,也就是分成3代,分别是0代,1代和2代。当达到阈值的时候才扫描。

当对象达到700个,才扫描0代链表

当0代链表扫描了10次,才扫描1代链表。

当1代链表扫描了10次,才扫描2代链表

过程:

把可能造成循环引用的对象先放到0代,当0代链表的对象超过700个就去扫描一次,扫描看看里面的对象有没有循环引用,有循环引用的对象计数器就减一,如果最后计数器是0了,就回收。如果扫描到的对象没有循环引用,就把它加到1代链表里面去。

Python的缓存机制

引用计数器为0时,不会真正销毁对象,而是将他放到一个名为 free_list 的链表中,之后再创建对象时不会在重新开辟内存,而是去free_list中获取对象,然后初始化数据,再加入到refchain
在这里插入图片描述
但是每种数据类型它的缓存机制可能不太一样。

  • float类型,维护的free_list链表最多可缓存100个float对象。

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

  • dict类型,维护的free_list数组最多也是可缓存80个dict对象,和list类似

  • 元组类型,特殊,元组类型维护一个20个元素的free_list,free_list里面的元素存的是索引,第一个元素,用来放只有一个元素的元组,一共可以放2000个。第二个元素,用来放2个元素的元组,也是一共可以放2000个,以此类推。

  • 也就是说:元组的free_list数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list数组中对应的链表,并添加到链表中。

  • int类型和字符型不是使用free_list,而是使用缓存池

  • int类型维护一个small_ints链表保存常见数据(小数据池),小数据池范围:-5 <= value < 257。即:重复使用这个范围的整数时,不会重新开辟内存。

    >>> a='c'
    >>> id(a)
    140674715138808
    >>> b='c'
    >>> id(b)
    140674715138808
    
  • 字符型维护一个unicode_latin1[256]链表,内部将所有的ascii字符缓存起来,以后使用时就不再反复创建。下图可以看到用的同一个内存地址

    >>> v1='a'
    >>> v2='a'
    >>> id(v1)
    140674715137248
    >>> id(v2)
    140674715137248
    
  • 除此之外,Python内部还对字符串做了驻留机制,针对那么只含有字母、数字、下划线的字符串(见源码Objects/codeobject.c),如果内存中已存在则不会重新在创建而是使用原来的地址里

    >>> v1='suzijian'
    >>> v2='suzijian'
    >>> id(v1)
    140674479927920
    >>> id(v2)
    140674479927920
    

:Python为了避免太频繁的创建值,它有一个池,这个池是启动解释器的时候自动帮我们创建的,比如解释器一启动,就会有一个池,里面放着-5,-4一直到256,这些值是Python认为最常用的数字,当我们去执行a=1,b=1的时候,不会去开辟内存,而是直接使用这个池里边的那个1,把它加到ref_chain里面。所以他们的内存地址也会一模一样,这个池里面的数据不会被回收,这样就减少了很多不必要的创建值和销毁值。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值