• 3. 垃圾回收机制


    1. 什么是 “垃圾” ?

      x = 1 //把 “1” 这个值赋给 “x”,在计算机中,先是定义了一个变量x,然后在内存里面开辟了一块内存空间,用来存放x的值,也就是1,这个时候,x和1是绑定的。

      这种绑定关系,叫做 “引用计数”。,这时候,“1”的引用计数就是1.

      如果在这个时候,再一次给x赋值,x = 2,那么这个时候,计算机就重新开辟了一块内存空间,用来存放x的值,也就是2,存放了2之后,x和之前的1就自动解绑了。

      这个时候,1的引用计数就变成0了,因为没有被引用,无法被访问到,计算机就把它视为“垃圾”。

    2. 什么是垃圾回收机制

    垃圾回收机制(Garbage Collection,简称 “GC”)是python解释器自带的一种机制,专门用来回收不可用变量值所占用的内存空间

    Python的垃圾回收机制主要采用的是引用计数为主、标记清除与隔代回收为辅的垃圾回收策略。

    3. 为什么要用垃圾回收机制?

    大多数程序在运行过程中会申请大量的内存空间,对于一些无用的内存空间如果不及时清理的话会导致内存使用殆尽(内存溢出),导致程序奔溃。

    因此内存管理是一件非常重要且繁琐的事情,而垃圾回收机制能够把程序猿从繁琐的内存管理中解放出来。

    4. 垃圾回收机制的原理

    4.1 引用计数

    x = 10  # 直接引用
    print(id(x))
    y = x
    z = x
    
    l = ['a.txt', 'b', x]  # 间接引用
    print(id(l[2]))  
    
    d = {'mmm': x}  # 间接引用
    
    print(id(d['mmm']))
    
    
    x=10
    l=['a.txt','b',x] # l=['a.txt'的内存地址,'b'的内存地址,10的内存地址]
    x=123
    print(l[2])    # 此时 x 的值变为 123 ,但l里x的值依然是10
    

    直接引用

    间接引用

    引用计数减少

    //值18的引用计数一旦变为0,其占用的内存地址就应该被解释器的垃圾回收机制回收

    4.1 标记清除

    4.1.1 循环引用——>导致内存泄漏问题

    l1=[111,] #此时l1被引用一次,引用计数为1
    l2=[222,] #此时l2被引用一次,引用计数为1
    
    l1.append(l2) # l1=[值111的内存地址,l2列表的内存地址]#此时l2又被引用一次,引用计数为2
    l2.append(l1) # l2=[值222的内存地址,l1列表的内存地址]#此时l1又被引用一次,引用计数为2
    
    print(id(l1[1])) #l1引用l2
    print(id(l2))
    
    print(id(l2[1])) #l2引用l1
    print(id(l1))
    
    print(l2)
    print(l1[1])
    #此时,l1和l2互相引用
    
    del l1 #l1引用次数-1
    del l2 #l2引用次数-1
    #此时直接引用解除关系,但间接引用还在循环引用,引用计数为1没有为0,但永远取不到值
    

    此时两个列表的引用计数均不为0,但两个列表不再被任何其他对象关联,没有任何人可以再引用到它们

    所以它俩占用内存空间应该被回收,但由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放,所以循环引用是致命的,这与手动进行内存管理所产生的内存泄露毫无区别

    所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题

    4.1.2 标记清除

    堆区与栈区

    ​ 在定义变量时,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。

    • ① 变量名与值内存地址的关联关系存放于栈区

    • ②变量值存放于堆区,内存管理回收的则是堆区的内容

    标记过程
    1. 遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象)
    2. 将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

    4.1.3 分代回收

    分代

    • 在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc对其扫描的频率会降低

    分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)

    1. 新定义的变量,放到新生代这个等级中

      假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一

    2. 当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代

      青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间

    3. 接下来,青春代中的对象,也会以同样的方式被移动到老年代中

      也就是等级(代)越高,被垃圾回收机制扫描的频率越低

    回收

    回收依然是使用引用计数作为回收的依据

    分代回收的缺点

    虽然分代回收可以起到提升效率的效果,但也存在一定的缺点:

    例如一个变量刚刚从新生代移入青春代,该变量的绑定关系就解除了,该变量应该被回收,但青春代的扫描频率低于新生代,这就到导致了应该被回收的垃圾没有得到及时地清理。

    没有十全十美的方案:

    毫无疑问,如果没有分代回收,即引用计数机制一直不停地对所有变量进行全体扫描,可以更及时地清理掉垃圾占用的内存,但这种一直不停地对所有变量进行全体扫描的方式效率极低,所以我们只能将二者中和。

    综上

    垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案目录

  • 相关阅读:
    [转] 数学原理浅谈一下《黑客帝国》
    [转] 应聘Java笔试时可能出现问题及其答案(第八部分)
    经典排序之堆排序
    求全排列算法简单的实现
    求一个整数因式分解
    adk环境变量配置
    经典排序之希尔排序
    快速求幂算法
    判断一个数字是否为素数的基于C语言的算法
    高次幂求模
  • 原文地址:https://www.cnblogs.com/j-chao/p/12896707.html
Copyright © 2020-2023  润新知