• Python是如何进行内存管理的?(图文讲解)


    在这里插入图片描述

    python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略

    一、对象的引用计数机制

    在这里插入图片描述

    一个变量指向了内存地址,引用计数为1
    两个变量同时指向了一个内存地址,引用计数为2

    在这里插入图片描述

    为什么引用计数为2呢?
    🔥🔥🔥注意:

    查看一个对象的引用计数: sys.getrefcount() 总是会比实际+1 ,因为 sys.getrefcount() 也调用了它一次 .但是print(sys.getrefcount(b))在执行完毕后引用就删除了。

    Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

    1、引用计数增加的情况:

    a、一个对象分配一个新名称

    a=4553223

    b、将其放入一个容器中(如列表、元组或字典)

    a=4553223
    b=a
    c=[a]
    c.append(a)
    print(sys.getrefcount(a))

    2、引用计数减少的情况:

    a、使用del语句删除引用

    del a

    b、引用超出作用域或被重新赋值

    def test():
        b=667787
    test()
    
    • 1
    • 2
    • 3

    函数执行完函数中的引用计数为0,可以进行回收

    二、垃圾回收

    1,当一个对象的引用计数归零时,它将被垃圾回收机制处理掉。

    2,当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

    举例:v1和v2互相引用,把v1和v2del

    v1 = [1, 5, 6]
    v2 = [6, 9, 2]
    v1.append(v2)
    v2.append(v1)
    
    del v1
    del v2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    在这里插入图片描述

    v1和v2对象被干掉了,但是堆内存中有相互引用,引用计数位为1;可是没有变量去接收,这些内存地址程序员想用都不能用到,并且还占用内存。解决办法就是用标记清除。

    3、标记清除

    在python的底层中,再去维护一个链表,这个链表中专门放那些可能存在循环引用的对象。

    标记清除算法是一种基于追踪回收 技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。

    4、分代回收(帮我们回收循环嵌套的引用)

    因为, 标记和清除的过程效率不高。清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。还有一个问题就是:什么时候扫描去检测循环引用?

    为了解决上述的问题,python又引入了分代回收。分代回收解决了标记清楚时什么时候扫描的问题,并且将扫描的对象分成了3级,以及降低扫描的工作量,提高效率。

    0代: 0代中对象个数达到700个(阈值),扫描一次。
    1代: 0代扫描10次,则1代扫描1次。
    2代: 1代扫描10次,则2代扫描1次。

    三、Python的内存池

    Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

    对于Python对象,以下几种情况,都有其独立的私有内存池。字符串的驻留机制

    1、字符串长度为0或者1
    2、符合标识符的字符串(只包含字符数字下划线)
    3、字符串只在编译时进行驻留,而非运行时
    4、[-5,256]之间的整数数字

    举例1:[-5,256]之间的整数数字

    值相同内存地址也相同

    >>> a=66
    >>> b=66
    >>> id(a)
    2526627451280
    >>> id(b)
    2526627451280
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    值相同内存地址不同

    >>> c=399
    >>> d=399
    >>> id(c)
    2526635259696
    >>> id(d)
    2526635259536
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    原理图如下:

    在这里插入图片描述

    举例2:符合标识符的字符串(只包含字符数字下划线)

    >>> a="qwe%"
    >>> b="qwe%"
    >>> a is b
    False
    >>> c="123kobe"
    >>> d="123kobe"
    >>> c is d
    True
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    举例3:字符串长度为0或者1

    >>> e=""
    >>> f=""
    >>> e is f
    True
    
    • 1
    • 2
    • 3
    • 4
    >>> e="!"
    >>> f="!"
    >>> e is f
    True
    
    • 1
    • 2
    • 3
    • 4

    举例4:字符串只在编译时进行驻留,而非运行时

    >>> a="kk"
    >>> b="k"+"k"
    >>> a is b
    True
    
    • 1
    • 2
    • 3
    • 4
    >>> a="kobe"
    >>> b="".join('kobe')
    >>> a is b
    False
    
    • 1
    • 2
    • 3
    • 4

    四、怎么优化内存管理

    1.手动垃圾回收

    先调用del a ; 再调用gc.collect()即可手动启动GC(嵌套的引用删除不了,因为引用计数为1)

    2.调高垃圾回收阈值

    gc.set_threshold 设置垃圾回收阈值(收集频率)。 将 threshold0 设为零会禁用回收。

    gc.set_threshold(800,20,20)
    
    • 1

    3.避免循环引用

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    Mac喵影工厂Wondershare Filmora X
    《软件方法》2023版第1章(09)基本共识上的沟通,SysML
    Soul App Android一二三面凉经(2024)
    C++缺省参数
    微信小程序开发常用的布局
    Spring框架-IOC
    HTTPS基础原理和配置 - 1
    2023年7月京东平板电脑行业品牌销售排行榜(京东销售数据分析)
    【CSS】CSS入门笔记第一弹~
    2024年山东省职业院校技能大赛中职组“网络安全”赛项竞赛试题-C
  • 原文地址:https://blog.csdn.net/YZL40514131/article/details/125884570