【python测试开发栈】—python内存管理机制(二)—垃圾回收

在上一篇文章中()中,我们介绍了python内存管理机制中的引用计数,python正是通过它来有效的管理内存。今天来介绍python的垃圾回收,其主要策略是引用计数为主,标记-清除分代回收为辅助的策略(熟悉java的同学回回忆下,其实这和JVM的策略是有类似之处的)。

引用计数垃圾回收

我们还接着上一篇文章来接着介绍引用计数的相关场景,方便我们来理解python如何通过引用计数来进行垃圾回收。其实通过字面意思,我们应该也不难理解,当一个对象的引用计数变为0时,表示没有对象再使用这个对象,相当于这个对象变成了无用的"垃圾",当python解释器扫描到这个对象时就可以将其回收掉。

我们通过一些例子来看下,可以使python对象的引用计数增加或减少的场景:

# coding=utf-8 """ ~~~~~~~~~~~~~~~~~ @Author:xuanke @contact: 784876810@qq.com @date: 2019-11-29 19:52 @function: 验证引用计数增加和减少的场景 """ import sys def ref_method(str): print(sys.getrefcount(str)) print("我调用了{}".format(str)) print('方法执行完了') def ref_count(): # 引用计数增加的场景 print('测试引用计数增加') a = 'ABC' print(sys.getrefcount(a)) b = a print(sys.getrefcount(a)) ref_method(a) print(sys.getrefcount(a)) c = [1, a, 'abc'] print(sys.getrefcount(a)) # 引用计数减少的场景 print('测试引用计数减少') del b print(sys.getrefcount(a)) c.remove(a) print(sys.getrefcount(a)) del c print(sys.getrefcount(a)) a = 783 print(sys.getrefcount(a)) if __name__ == '__main__': ref_count()

运行结果如下:

测试引用计数增加 7 8 10 我调用了ABC 方法执行完了 8 9 测试引用计数减少 8 7 7 4

从上面的结果我们得出以下结论:

引用计数增加的场景:

对象被创建并赋值给某个变量,比如: a = 'ABC'

变量间的相互引用(相当于变量指向了同一个对象),比如:b=a

变量作为参数传到函数中。比如:ref_method(a),其实上一篇文章,我们也提过调用getrefcount会使引用计数增加。

将对象放到某个容器对象中(列表、元组、字典)。比如:c = [1, a, 'abc']

引用计数减少的场景:

当一个变量离开了作用域,比如:函数执行完成时,上面的运行结果中,不知道大家发现没,执行方法前后的引用计数保持不变,这就是因为方法执行完后,对象的引用计数也会减少,如果在方法内打印,则能看到引用计数增加的效果。

对象的引用变量被销毁时,比如del a 或者 del b。注意如果del a,再去获取a的引用计数会直接报错。

对象被从容器对象中移除,比如:c.remove(a)

直接将整个容器销毁,比如: del c

对象的引用被赋值给其他对象,相当于变量不指向之前的对象,而是指向了一个新的对象,这种情况,引用计数肯定会发生改变。(排除两个对象默认引用计一致的场景)。

引用计数虽然可以实时的知道某个对象是否可以被回收,但是也有两个缺点:

需要额外的空间维护引用计数。

遇到有循环引用的对象,无法有效处理。所谓循环引用就是比如:对象A引用了对象B,而对象B又引用了对象A,造成它们两个引用计数都不能减少到0 ,因此不能被回收。

标记-回收垃圾回收

为了解决引用计数法无法解决的循环引用问题,python采用了标记-回收垃圾回收算法,它的整个过程分为两步:

标记: 遍历所有的对象,如果是可达的(reachable),也就是还有对象正引用它,那么就标记该对象为可达;

清除: 再次遍历所有的对象,如果某个对象没有被标记为可达,则将其回收掉。

需要注意的是在python中可以产生循环引用问题的可能是:列表、字典、用户自定义类的对象、元组等对象,而对于数字字符串这种简单的数据类型,并不会产生循环引用,因此后者并不在标记清除算法的考虑之列。

针对标记-回收垃圾回收的过程,我从网上找了几张图片,方便大家来了解整个过程:

1.png

第一张图是初始状态,图片上不仅有ref_count,还有一个gc_ref的值,这个gc_ref其实就是为了来解决引用计数问题的,它是ref_count的一个副本,所以它的初始值和ref_count保持一致。当开始遍历所有对象时,当发现link1引用了link2对象时,会将link2的gc_ref值减少1,如此类推,就得到下图的结果。

2.png

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzjydy.html