• 我也来扒一扒python的内存回收机制!


    python的内存回收是面试中经常会问到一个问题,今天我来给大家深度剖析下python的内存回收和缓存机制

     

    1、引用计数器

    我们知道,python是通过引用计数器来做内存回收的,下面我们来重点讲下引用计数器

    提到引用计数器,我们需要先讲下python中的环状双向链表refchain。

    1.1 双向链表refchain

    python程序中,创建的任意一个对象,都会加到这个refchain双向链表中

     不同的类型的对象在放到refchain中会有不同的地方,也会有相同的地方

     

    1.2 refchain结构体

    可以看下,cpython中的源码中定义的结构体

     

     

    PyObject这个结构体封装了四个值,其他类型的对象会基于PyObject这个结构体作为基类,在封装其他需要的类型

    下面我们看不同给类型的结构体封装格式

    比如float类型

     

    比如int类型

     

     list类型

     

     

    tuple类型

     

    dict类型

     

    1.3 引用计数器

    我们上面讲的 引用计数器

    Ob_refcnt就是引用计数器,默认是1,当有其他对象引用对象的时候,这个值就会发生变化

     

    1
    2
    3
    4
    5
    name = "test"  #ob_refcnt的值是1
     
    new = name      #ob_refcnt的值是2
     
    del new        #ob_refcnt的值是1

     

      

     

     

     

     

    ob_refcnt0的时候,就会对该对象做垃圾回收,会做两件事情

     

     1、refchain双向链表中移除

     

    2、将这个对象进行销毁,归还内存给操作系统

     

    2、标记清除

    大家认为引用计数器的方式很牛逼,但是其实这里有个场景,引用计数器是解决不了的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 存在双向引用的场景,引用计数器就会出问题
    v1 = [1,2,3]
     
    v2 = [4,5,6]
     
    v1.append(v2)
     
    v2.append(v1)
     
    # 此时
    # v1的ob_refcnt为2
    # v2的ob_refcnt为2
     
     
    del  v1
    del  v2
     
    # 此时
    # v1的ob_refcnt为1
    # v2的ob_refcnt为1
    #
    # 此时v1和v2不会被回收,但是其实已经没有对象引用v1和v2了 此时就会出现内存泄露的现象

      

    为了解决上面的场景,python又引入了标记清除

     

    python底层,会维护另外一个链表(A),这个链表中存放可能存在双向应用的对象。在python中,只有listtupuledictset会存在双向引用的场景,如果我们创建这样的对象,这个对象会被存在到两个链表中

     

    python内部,会有规律的扫描这个链表A中的每个元素,检查是否有双向引用,如果有,会让双方的引用计数器分别减1,然后在判断ob_refcnt来判断是否做垃圾回收

    3、分代回收

     

    那在链表A中,扫描一次链表A还是比较耗时的,因为每个元素都要扫描一次,扫描一次的代价比较大,python是以什么规律下会触发扫描链表A呢?

     

     

     

    在分代回收中,把链表A中的数据,也就是可能存在双向引用的元素,划成3个链表,依次来提升扫描的效率

     

     

     

     0代:0代中的对象个数达到700个,在触发扫描一次0代链表;第一次扫描0代中的对象,如果0代中有垃圾,则回收,如果不是垃圾,则清空0代,把不是 垃圾的对象放到1

     

     

     

    1代:0代扫描超过10次,则1代扫描一次

     

    如果1代中有垃圾,则回收,如果不是垃圾,则清空1代,把不是 垃圾的对象放到2

     

     

     

     2代:1代扫描超过10次,则2代扫描一次

     

    如果2代中有垃圾,则回收,如果不是垃圾,则清空2

     

    4、缓存机制

     

    Python中还有些内存管理的机制,用来优化性能,就是这里准备讲的缓存机制

    4.1 池

     

    python中,为了避免重复申请内存和销毁内存,python会对一部分常见的对象,会提前把这些常见的对象提前申请好

     

     

     

    Int类型是用池来做缓存

     

     

     

    比如 -5-4 .。。。。。。。256  这部分对象python认为非常常用,会在python启动的时候提前创建好对象,且不会去走销毁流程,

     

     

     

    可以看到v1v2的内存地址是一样的

     

     

    4.2 free_list机制

    Free_list机制(floattupulelistdict为典型代表)

     当引用计数器ob_refcnt0的时候,按理说应该回收的,但是在python中,为了优化性能,不会回收,而是将对象添加到free_list链表中,当作缓存,以后再次创建相同的对象,就会重新创建对象,而是直接使用free_list中的对象

     

     

    1
    2
    3
    4
    5
    v3 = 3.14
     
    del  v3  #会放到free_list中
     
    v4 = 4.14   #会对v3的内存地址重新赋值,就不需要重新申请内存

     

      

    float类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # float类型,维护free_list链表中最多可以缓存100个float对象
    v9 = 3.14
    print(id(v9))
    del  v3  #会放到free_list中
     
    v10 = 3.14   #会对v3的内存地址重新赋值,就不需要重新申请内存
    print(id(v10))
     
    # 当前引用计数器为0的时候,会先去判断free_list是否满,未满在缓存到free_list中,满了则销毁

      

    list类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # list类型,维护一个free_list对多可缓存个80个list对象
     
     
    v11 = [1,2,3]
    print(id(v11))
    del v11
     
    v11 = ["2b","2b"]
    print(id(v11))
     
    # 输出
    # 2303949405888
    # 2303949405888

      

     

    dict类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # dict类型,会维护一个free_list最多可缓存80个dict对象
    v13 = {"k1":"v1","k2":"v2"}
    print(id(v13))
    del v13
     
    v13 = {"k3":"v1","k4":"v2"}
    print(id(v13))
     
    # 2291100371392
    # 2291100371392

      

    tuple类型

    会维护一个20个元素的free_list的表。其中0号元素,缓存在只有一个元素的tuple,1号元素缓存只有2个元素的tuple。。。。。。20号元素缓存只有21个元素的tuple。其中每个元素中最多可以存储2000个列表

     

    str类型

    1、首先会把所有的ascii码元素全部会缓存起来,不会销毁

    2、除此之外,python还对常用的字符串做了驻留机制,争对只有数字,字母,下划线组成的字符串做了驻留缓存,如果内存中存在相同的值,则不会去重新申请内存,而是直接使用驻留内存中的地址

     

  • 相关阅读:
    深入JTS事务引擎:理论与实践相结合,掌握高效事务管理的秘诀
    应用层-HTTP协议
    pbootcms模板提交留言表单后,如何跳转到指定的网址?
    现代修谱有4个事情尽量不要做,避免掉坑
    【ROS2原理3】:构建系统“ament_cmake”和构建工具“ament_tools”
    第3/100天 阅读笔记
    bootstrap学习:自定义栅格系统
    嵌入式设备视频编码比较:H.264、H.265、MPEG-2和MJPG
    ubuntu install Python3
    【linux】chmod命令
  • 原文地址:https://www.cnblogs.com/bainianminguo/p/17644807.html