python3内存回收__动态类型 , 可变数据类型 , 引用计数 , 引用减少 , 垃圾回收 , 分代回收 , 孤立的引用环

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> python3内存回收__动态类型 , 可变数据类型 , 引用计数 , 引用减少 , 垃圾回收 , 分代回收 , 孤立的引用环

点击上方”python宝典”,关注获取python全套视频,

技术文章第一时间送达!

1.动态类型

  • 对象是存储在内存中的实体。但我们并不能直接接触到该对象。
  • 引用与对象分离是动态类型的核心。
  • 引用与对象分离是动态类型的核心。

    (一)不可变数据类型:

    
    # --------------------引例1
    a = 1
    b = a
    a = a + 2
    print(a, b)
    
    OUTPUT:
    -- 3  1
    
    
    # --------------------引例2
    lt = [1, 2, 3]
    lt2 = lt
    lt = 4
    print(lt, lt2)
    
    OUTPUT:
    -- 4  [1, 2, 3]
    
    
    # 说明:
        1.开始a和b为指向1的两个引用
        2.第三个表达式中a重新赋值,指向了新的对象3
    
    # 总结:
        即使多个引用指向同一对象,若一个引用值发生变化,那么实际上是该引用指向一新引用,并不影响其他的引用的指向。
    

    (二)可变数据类型:

    以列表为例:

  • 列表相当于一个引用的集合,每一个元素相当于一个引用(lt[0], lt[1], lt[2])
  • 下部代码中,lt[0] = 11这一操作,改变的是第一个元素(引用)的指向,而不是lt的指向。故所有指向该lt对象的引用均会受到影响。
  • 下部代码中,lt[0] = 11这一操作,改变的是第一个元素(引用)的指向,而不是lt的指向。故所有指向该lt对象的引用均会受到影响。

    python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环
    
    lt = [1, 2, 3]
    lt2 = lt
    lt[0] = 11
    
    print(lt, lt2)
    
    OUTPUT:
    -- [11, 2, 3]
    -- [11, 2, 3]
    

    2.python内存回收机制

    (一)对象的内存使用

  • python为动态类型编程语言。对象与引用相分离。
  • id(对象):查看对象的内存地址
  • python当中会缓存整数、浮点数、字符串、空元组、空集合,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些引用均指向同一个对象。
  • is 关键字用于判断两个引用的对象是否相同。
  • id(对象):查看对象的内存地址

    is 关键字用于判断两个引用的对象是否相同。

    
    # python当中会缓存整数、短小字符,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些引用均指向同一个对象。
    # --------------------1.整数:True
    a = 1
    b = 1
    print(id(a), id(b))
    print(a is b)
    
    # --------------------2.浮点数:True
    a = 1.0
    b = 1.0
    print(id(a), id(b))
    print(a is b)
    
    # --------------------3.短字符串:True
    a = "good"
    b = "good"
    print(id(a), id(b))
    print(a is b)
    
    # --------------------4.字符串:True
    a = "very good morning this is linux 123 456 789 10111213"
    b = "very good morning this is linux 123 456 789 10111213"
    print(id(a), id(b))
    print(a is b)
    
    # --------------------5.列表:False
    a = [1, 2]
    b = [1, 2]
    print(id(a), id(b))
    print(a is b)
    
    # --------------------6.元组(非空):False
    a = (1, 2)
    b = (1, 2)
    print(id(a), id(b))
    print(a is b)
    
    # --------------------7.集合:False
    a = set([1, 2])
    b = set([1, 2])
    print(id(a), id(b))
    print(a is b)
    
    # --------------------8.字典:False
    a = {"name": "mx"}
    b = {"name": "mx"}
    print(id(a), id(b))
    print(a is b)
    

    (二)引用计数(跟踪和回收垃圾)

  • 可通过sys包中的getrefcount(引用名)来查看某个对象的引用计数
  • 当将某个引用作为实参传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,引用计数结果比实际值多1
  • 对于python的容器(container)对象,如:列表、字典等,其内部包含的并不是对象,而是对象的引用。
  • 词典对象用于记录所有全局变量的引用。可通过内置函数globals()查看该词典
  • 容器对象的引用可能会构成很复杂的拓扑结构。可通过objgraph包中的show_refs()函数来进行查看 6.objgraph包的安装(windows):pip install xdot   /   pip install objgraph 展示(# 3 中对象引用的拓扑结构):

  • 当将某个引用作为实参传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,引用计数结果比实际值多1

    词典对象用于记录所有全局变量的引用。可通过内置函数globals()查看该词典

    python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环
    
    from sys import getrefcount
    import objgraph
    
    
    lt1 = [0, 8, 2, 4]
    lt2 = lt1
    
    # 1.查看对象 [0, 8, 2, 4]的引用计数
    print(getrefcount(lt1) - 1)
    
    # 2.查看词典(记录全局变量的引用)对象
    print(globals())
    
    # 3.查看容器对象引用的拓扑结构
    x = [1, 9, 9, 5]
    y = [x, dict(key1=x)]
    z = [y, (x, y)]
    
    # def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
    #               highlight=None, filename=None, extra_info=None,
    #               refcounts=False, shortnames=True, output=None)
    # max_depth / too_many: 限制图形的深度和宽度
    # extra_ignore / fileter:删除图标中不需要的对象
    # hightlight:以蓝色突出显示某些图形结点
    # filename / output:``output`` and ``filename`` should not both be specified.
    # extra_info:显示对象的额外信息
    # refcounts: 是否查看对象引用计数
    objgraph.show_refs([z], filename="ref_topo.dot")
    

    (三)引用减少

    python内置关键字del删除的是对象的引用,而不是内存中的对象。

    
    from sys import getrefcount
    
    
    a = [1, 2, 3]
    b = a
    c = a
    print(getrefcount(a))
    del c
    print(getrefcount(a))
    del b
    print(getrefcount(a))
    

    (四)垃圾回收

  • 当python某个对象引用计数为0,说明该对象无引用。python会启动“垃圾回收”,将无用的对象清除(从内存中清除) 问题:频繁的垃圾回收,会大大降低Python的工作效率。故,python只会在特定的条件下,自动启动垃圾回收。

  • python通过阙值( |“分配对象次数” - “取消分配对象次数”| )来判断是否进行垃圾回收。(高于阙值则进行垃圾回收)
  • 可通过gc包的get_threshold()函数查看阙值大小;set_threshold()函数设置阙值大小。
  • python通过阙值( |“分配对象次数” - “取消分配对象次数”| )来判断是否进行垃圾回收。(高于阙值则进行垃圾回收)

    
    from sys import getrefcount
    import gc
    
    
    a = [1, 2, 3]
    b = a
    c = b
    print(getrefcount(a) - 1)
    
    print(gc.get_threshold())
    gc.set_threshold(300)
    print(gc.get_threshold())
    
    
    OUTPUT:
    -- (700, 10, 10)
    -- (300, 10, 10)
    
    # ---------------------------说明-------------------------------- #
    #     结果中的第一个参数代表阈值的大小
    #     第二个参数代表“每100代垃圾回收,会配合一次1代垃圾回收”(分代回收)
    #     第三个参数代表“每101代垃圾回收,会配合一次2代垃圾回收”(分代回收)
    #     后两个参数同样是通过gc.set_threshold()函数进行修改
    # -----------------后两个参数涉及到分代回收的问题------------------- #
    

    (五)分代回收(以空间换时间进一步提高垃圾回收效率)

  • python同时采用分代回收策略。该策略假设:存活时间越久的对象,越不可能在后边的程序当中编程垃圾。
  • python将所有对象分为0, 1, 2三代对象。
  • 所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动 时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一 定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描

  • 查看和修改如(四)中代码所示。
  • python将所有对象分为0, 1, 2三代对象。

    查看和修改如(四)中代码所示。

    (六)孤立的引用环

    
    lt1 = [1, 2, 3]
    lt2 = [lt1]
    
    lt1.append(lt2)
    
    del lt1
    del lt2
    

    说明:

    上边代码块中创建了两个列表对象lt1, lt2 。这两个列表对象相互引用,形成孤立的引用环。当删除lt1, lt2的时候,以上两个列表对象在程序中将无法被调用,但是其实际的引用计数并不为0,不会被垃圾回收。

    为了回收这样的引用环,python复制每一个对象的引用计数(lt1:1, lt2:1)。然后,python遍历所有的引用环涉及到的对象,该处仅有lt1 和lt2 ,当遍历到lt1时,由于lt1引用了lt2, 故将lt2的引用计数减1。同理,当遍历到lt2的时候将lt1的引用计数减1,结果他们的值都为0,最后将不为0的对象保留,为0 的对象进行垃圾回收。

    python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

    但是这样就有一个问题,假设对象A有一个对象引用C,而C没有引用A,如果将C计数引用减1,而最后A并没有被回收,显然,我们错误的将C的引用计数减1,这将导致在未来的某个时刻出现一个对C的悬空引用。这就要求我们必须在A没有被删除的情况下复原C的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

    因此,“标签-清除”方法显得更好。

    (七)标签 - 清除法

    首先,他先划分出两拨,一拨叫root object(存活组),一拨叫unreachable(死亡组)。然后,他把各个对象的引用计数复制出来,对这个副本进行引用环的摘除。摘除完毕,a和c的引用计数副本为0,b的引用计数副本为1,则将那么先把副本为非0的放到存活组(b),副本为0的打入死亡组(a, c)。那么此时若将引用计数为0的对象从内存中清除,则b在引用c的时候就会产生对c的悬空引用。为解决这种问题,python会在存活组中对每个对象都分析一遍,由于目前存活组只有b,那么他只对b分析,因为b要存活,所以b里的元素也要存活,于是在b中就发现了原a所指向的对象,于是就把他从死亡组中解救出来。至此,进过了一审和二审,最终把所有的任然在死亡组中的对象通通杀掉,而root object继续存活。b所指向的对象引用计数依然是2,原c所指向的对象的引用计数仍然是1。

    python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

    作者:admin_maxin
    原文:https://blog.csdn.net/admin_maxin/article/details/81632580 

    python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

    识别图中二维码,欢迎关注python宝典

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> python3内存回收__动态类型 , 可变数据类型 , 引用计数 , 引用减少 , 垃圾回收 , 分代回收 , 孤立的引用环


     上一篇
    列表推导式和生成器,单例。 列表推导式和生成器,单例。
    点击上方”python宝典”,关注获取python全套视频, 技术文章第一时间送达! python3中列表推导式和生成器的不同:(1)列表推导式是将所有的值一次性加载到内存中生成器是将列表推导式的[]改成(),不会将所有的值一次性加载到内存
    2021-04-05
    下一篇 
    python爬虫之腾讯视频vip下载 python爬虫之腾讯视频vip下载
    点击上方”python宝典”,关注获取python全套视频, 技术文章第一时间送达! 运行环境IDE:pycharmpython:3.6.5 实现目的实现对腾讯视频目标url的解析与下载,由于第三方vip解析,只提供在线观看,隐藏想实现对目
    2021-04-05