04-python垃圾回收机制

python垃圾回收机制

一、引入

解释器在执行定义变量的语法时,会申请内存空间来存放变量值,每一块内存空间都有其唯一的内存地址,我们在前面说过,变量名并不是存放的变量值,而是存放的内存地址,通过访问内存地址,就能找到对应的值,而内存的容量是有限的,因此必然会有一些内存空间被回收然后再提供给用户使用,这就是回收内存空间。

那么回收哪些值所在的内存空间呢?或者说,存放哪些值的内存就应该被回收呢?答案:没有用了的值就会变成垃圾就会被回收掉。那么问题又来了,哪些值是没有用的呢?怎么判断一个值是否有用,是否无用呢?

二、什么是垃圾?

前面我们说到,没有用的值就会变成垃圾,其所在的内存空间就会被回收掉。而我们定义变量去存储值的目的也是为了使用值,而访问变量值需要通过直接引用(比如x=10,直接使用x即可)或者间接引用(l = [x,20],10被x直接引用,从而通过列表l可以间接访问到10),所以当一个变量值不再绑定任何引用时,我们就无法再访问到该变量值了,该变量值就成了垃圾

三、垃圾回收机制

垃圾回收机制(简称GC)是python解释器自带的一种机制,专门用来回收没有绑定任何引用(直接引用和间接引用)的变量值所在的内存空间

四、理解GC需要的储备知识

1、堆区与栈区

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

堆区与栈区

当我们执行x = y时,就会变成

堆区与栈区2

此时x和y指向同一块内存空间,当我们修改y所绑定的值的时候,同时也会修改x

2、直接引用和间接引用

直接引用:从栈区出发直接通过内存地址找到堆区对应的值

间接引用:从栈区出发在堆区中找到内存地址,再用找到的内存地址再在堆区中寻找对应的值

通过下面的图来进一步理解

x = 10
y = 20
l = [x,y]

x,y和列表l在内存中的实际存储方式是下图中这样的

直接引用和间接引用

五、GC详解

python中的垃圾回收机制主要是应用了“引用计数”来跟踪和回收垃圾

1、引用计数

引用计数:变量值被变量名关联的次数

例如:age = 18,变量值18倍变量名age关联了,则18的引用计数为1

此时,再另age_of_cuihua = age,则age_of_cuihua和age都关联了18,此时18的引用计数增加为2,

如果另age = 80,此时age和80建立了起了联系,age和18就没有联系了,则18的引用计数减少为1,

我再del age_of_cuihua(del的意思就是解除变量名与变量值之间的绑定关系),此时18的引用计数为0。

当变量值的引用计数变成0时,就会触发垃圾回收机制,18所在的内存空间就会被回收掉

2、引用计数产生的问题

2.1、问题一:循环引用

l1 = ['pig'] # 此时,列表1被引用了一次,列表1的引用计数变成了1
l2 = ['dog'] # 此时,列表2被引用了一次,列表2的引用计数变成了1

# 要放大招了
l1.append(l2) # l1列表中添加l2,此时列表l2又被引用了一次,引用计数变成了2
l2.append(l1) # l2列表中添加l1,此时列表l1又被引用了一次,引用计数变成了2

# ??????这是什么操作,我们打印出来看一看

print(l1) # ['pig', ['dog', [...]]]
print(l2) # ['dog', ['pig', [...]]]

让我们此时画图来分析一下其中的原理

循环引用

这就是循环引用,我中有你,你中有我

此时做一个大胆的操作

del l1
del l2

????这次又发生了什么,我们再画一个图看看

循环引用2

上面说过,del的作用是解除变量名与变量值之间的绑定关系,那么此时已经无法通过变量名l1和l2访问两个列表了,这两个列表的引用计数再次由2减少成了1。

但此时,我们惊人的发现,已经没有任何办法去访问两个列表了,那么这两个列表已经没用了,正式宣告成为了垃圾,是应该被回收的,但是这两个列表的引用计数还为1,是不能被回收的,这就和我们之前说过的根据引用计数回收垃圾相矛盾了,即按照引用计数回收垃圾,这个方法竟然失灵了。

也就是说,引用计数带来的影响很致命,如果我们大量使用引用计数,那么,我们的内存空间,会被一点一点的吞噬掉,该怎么解决这个问题呢?

2.2、解决方案:标记-清除

标记-清除原理:当应用程序可用的内存空间被耗尽时,就会停止整个程序,然后进行两项工作,标记和清除

标记:通俗的讲就是,栈区相当于“根”,凡是通过根出发可以直接访问或者间接访问到的都是“有根之人”,有跟之人当活,无根之人当死;具体的讲,标记的过程其实就是遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以称之为GC Roots对象),然后将其中可以直接或者间接访问到的对象都标记为可以存活的对象,其余的为非存活对象,应该清除掉

清除:清除所有没有被标记的非存活对象

基于该算法,当我们解除掉变量名l1和l2与两个列表中的绑定关系的时候,两个列表会被标记为非存活对象,等待清除,无需我们手动去清除

2.3、问题二:效率问题

标记-清除需要遍历堆区中所有的对象,每次回收内存,都要遍历一遍,这个工作量是非常庞大的,因此即为耗时,于是引入了“分代回收”算法,分代回收采用了以空间换时间的策略

2.4、解决方案:分代回收

分代回收原理:分代指的是根据存活时间来划分不同等级(也就是不同的代),假如分成了幼儿代,青春代,中年代,老年代五个代,一个变量一开始假如在幼儿代,一段时间内被扫描了三次,依然有绑定关系,此时就把这个变量移入更加高级的青春代,扫描的时候会优先扫描幼儿代,然后再扫描更高级的代

虽然分代回收能起到提高效率的效果,但也存在着一定的问题,比如一个变量刚从幼儿代移入了青春代,但它的绑定关系被解除,此时不会及时的扫描到这个变量,释放内存

 

posted @ 2021-03-03 14:54  田彦龙  阅读(30)  评论(0编辑  收藏  举报