Loading

Python之垃圾回收

一:垃圾回收机制

解释器执行到变量定义时,会申请内存存放变量的值,执行期间可能发生value改变,也可能发生id的改变,一旦id变了,原来的值就成了垃圾,就应该回收掉它占的内存。

变量名是我们访问变量值的唯一途径,当二者不再关联,变量值就是垃圾,内存空间的申请和回收是十分复杂,且重复次数很多的操作,不能让回收操作插入在你的逻辑代码中,因此python语言本身提供了垃圾回收机制,让我们专注于业务逻辑代码编写。

二:为什么要回收垃圾

内存的大小不是无限的,随着程序的执行,如果没用的数据一直不回收,导致内存使用殆尽,此时程序就会崩溃,无法对外服务这是致命的。

python自带GC让我们从复杂的内存管理中释放出来。

三:垃圾回收的原理

算法有:引用计数法  标记清除算法  分代回收算法

3.1引用计数算法

变量名和变量值的关联次数。

name="zhangsan"

myname=name

此时变量值“zhangsan”被关联两次,只针对数据,对于变量名来讲他占用内存很少,GC回收变量名不需要复杂的算法,只要没有关联变量值,变量名也是垃圾直接就被回收了。

但是对于变量值来说就复杂多了,del myname此时引用计数-1,因此引用计数有个缺点就是频繁的+-,这只是其一

第二个缺点是致命的,就不是效率的问题了,循环引用的bug(也叫交叉引用)

l1=[1,2,3]
l2=["a","b"]
l1.append(l2)
l2.append(l1)
del l1
del l2

前两行代码使得l1,l2指向的变量值引用计数分别为1,

l1.append(l2)

l2.append(l1)

这之后l2 l1计数分别+1为2然后故意的断掉l1 l2和变量值的关联,此时计数-1变成了1,此时两个列表都应该被回收掉,但是因为计数为1,GC无法回收掉这两个列表,结果就是值的引用计数不为0,不会被回收掉,造成了内存泄漏

因此python引入了标记清除算法,还有分代回收来提高效率

3.2标记清除算法

此算法望文生义,就是分标记和清除两个步骤

变量名在栈中存放,变量值在堆中存放,二者除了自己本身之外,变量名有地址,变量值也有地址,地址关联关系存放在栈区。就是说地址的映射关系表保存在栈中。

标记:GC roots对象,栈区或线程都可以作为GC roots对象,从这些对象出发,一根线开始穿把可以直接和间接访问到的对象都像针一样穿到线上,视为可存活对象,剩下的全部视为不可存活对象。

清除:将所有没有在线上的对象全部从堆中清除掉。

上面循环应用的两个列表,在栈中没有直接引用,都是在堆里面的简介引用,但是二者都不是GC roots对象,因此会被回收掉,解决了循环引用的bug。

3.3分代回收算法

引用计数的毛病就是频繁+-效率低,每次回收内存就像for循环一样去挨个的判断计数是否为0,十分的费时,分代回收算法是空间换时间。

引用计数所有的对象一视同仁都判断引用次数,而分代回收则是把对象划分了等级,每进行一次GC只要对象活下来就会权重+1,超过阈值就会进入下一个等级,一共分新生代,青春代,老年代三个等级,等级越低检查越频繁即GC的越频繁,这样就实现的效果是越是新创建的对象回收的几率越高,权重越高的对象是垃圾的几率越低,那么就隔得时间长一点再判断是不是垃圾。

这里只是类比,并不是真实的时间,新生代30秒一检查遍历引用计数是否为0,青春代1分钟检查一次,老年代5分钟检查一次。

这样GC的效率得到了提升。

但是分代回收有一个缺点就是,从青春代刚进入老年代就变成了垃圾,但是GC没有及时回收掉此对象,造成了GC延迟。

总结

GC的时候程序是暂停的,但是我们无法感知,因为回收的速度很快。

 

 



posted @ 2019-11-05 15:56  浅忆尘  阅读(145)  评论(0)    收藏  举报