内存管理:垃圾回收机制
什么是垃圾回收机制?
"""
垃圾回收机制(简称GC)是Python解释器自带的一种机制,专门用来回收不可用的变量值所占用的内存空间,当一个变量值被绑定的变量名的个数为0时,该变量值无法被访问到,称为垃圾。
"""
例如:
引用计数增加
x = 1 //1的引用计数为1
y = x //1的引用计数为2
z = x //1的引用计数为3
引用计数减少
del x //解除变量名x与值1的绑定关系,1的引用计数变为2
del y //解除变量名y与值1的绑定关系,1的引用计数变为1
当我们重新给z赋值时:
z = 2 //解除了变量名z与值1的绑定关系,1的引用计数变为0
所以当1被绑定的变量名的个数为0时,该变量值无法被访问到,就变成了垃圾。
引用计数有直接引用与间接引用两种方式
例如:
x = 1 //属于直接引用
y = x //属于间接引用
z = y //属于间接引用
或在列表和字典中
x = 1
y = [2,x] //属于间接引用
print(y) //结果[2, 1]
z = {"age":x} //属于间接引用
print(z) //结果{‘age‘: 1}
引用计数存在的问题
"""
1,维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比
2,循环引用(也称交叉引用)
循环引用会导致:值不再被任何名字关联,但是值的引用计数并不会为0,应该被回收但不能被回收
例如:list1 = ["佩奇"] //列表1被引用一次,列表1的引用计数变为1
list2 = ["乔治"] //列表2被引用一次,列表2的引用计数变为1
list1.append(list2) //把列表2追加到l1中作为第二个元素,列表2的引用计数变为2
list2.append(list1) //把列表1追加到l2中作为第二个元素,列表1的引用计数变为2
当我们del删除时,值的引用计数为1,并不会为0
所以为了解决这两个致命弱点,Python引入了两种GC机制解决方案:标记和清除
"""
标记和清除
"""
标记清除算是一种基于追踪回收技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。
容器对象(比如:list,set,dict,class,instance)都可以包含对其他对象的引用,所以都可能产生循环引用。而“标记-清除”计数就是为了解决循环引用的问题
1、标记
标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
2、清除
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
"""
分代回收
为什么用分代回收?
基于引用计数的回收机制,每次回收内存时,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率,分代回收采用的是用“空间换时间”的策略。
"""
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代), 假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。所以,老年代中的对象是存活时间最久的对象。
综上所述垃圾回收机制是在清理垃圾释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案。
"""
python垃圾回收机制
原文:https://www.cnblogs.com/zylwy/p/14081503.html