Python中的垃圾回收机制是自动的,它主要使用了引用计数来追踪和回收内存。
1、引用计数
当一个对象被创建时,就会被分配一块内存,然后将其引用计数设置为1。当另一个变量引用该对象时,引用计数就会增加1。当一个对象的引用计数变为0时,说明该对象没有任何引用,Python解释器就会将其回收并释放相应的内存。
下面给个例子去解释引用计数
1、误区例子
import sys
a = 1
print(f'对象a:{sys.getrefcount(a)-1}')
b = 10
print(f'对象a:{sys.getrefcount(a)-1},对象b:{sys.getrefcount(b)-1}')
# 输出结果:
# 对象a:96
# 对象a:96,对象b:14
如图,我这a,b在例子中只引用了一次,但输出结果可知a和b的引用计数不是一次;是因为python自带的由整数常量池 [-5, 256] ,在这个区间的整数,被称为小整数对象,所以自定义一个其他类型的数据。
那我用一个整数常量池之外的数据尝试一下
在 .py文件中,内存地址是一样的:
在终端执行,内存地址是不一样的:
由两图可知,解释器将 .py文件进行编译的时候,逐行进行解释,发现a和b的变量的内存地址一样,解释器会优化代码,则将两个变量指向同一块内存地址,以减少内存的开销。
2、正确例子
import sys
class X:
pass
a = X() # a引用次数加1
print(f'对象a:{sys.getrefcount(a)-1}')
c = a # a引用次数加1, c引用次数为a的引用次数2
print(f'对象a:{sys.getrefcount(a)-1}, 对象c:{sys.getrefcount(c)-1}')
# 输出结果:
# 对象a:1
# 对象a:2, 对象c:2
引用次数加1条件:
对象被创建,例如a=1
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如 a_list=[a,a]
引用次数减1条件:
对象被显式销毁,如del a。
变量重新赋予新的对象,例如a=1。
对象离开它的作用域,如函数执行完毕时,函数中的局部变量(全局变量不会)。
对象所在的容器被销毁,或从容器中删除对象。
这样的缺点是:假如循环引用的话,其引用计数一直不为0,就不会被回收,就会浪费资源,就引进了下面两种方式进行回收。
2、标记&清除
标记:标记所有能够被程序访问到的对象(root objects),如全局变量、栈中的变量等,将它们标记为“活跃”对象;对于标记的对象,遍历它们的引用,将引用的对象标记为“活跃”对象;
清除:对于未被标记的所有对象,将它们标记为“垃圾”,即将它们回收清除。
class X:
def aaa(self):
return 1
def func():
a3 = X().aaa()
a2 = 3 + a3 # 为了表明a2是跟a3相关的
a1 = a2 + a3 # 为了表明a1是跟a2,a3相关的
c1 = a2
c2 = c1 + a1
b1 = X()
b2 = X()
b1.obj = b2
b2.obj = b1
return [a1, a2, a3]
A = func()
如图,A,a1,a2,a3,b1,b2,c1,c2都是被标记的对象;其中A,a1,a2,a3都被标记为“活跃”对象,b1,b2,c1,c被标记为“垃圾”;垃圾就进行回收,“活跃”对象就进行保存。
因为只有“活跃”对象到最后一步被使用,“垃圾”没有被使用
3、分代回收
1、将所有收集的对象放到一起;
2、遍历集合中的所有对象,将其分为两个引用计数为0和引用计数大于0的两个集合;
3、大于0的放到更老的一代,等于0的进行回收
4、或者是触发了GC中的阈值,进行回收
设置各世代的阈值:
import gc
threshold = gc.get_threshold()
print("各世代的阈值:", threshold)
# 设置各世代阈值
# gc.set_threshold(threshold0[, threshold1[, threshold2]])
gc.set_threshold(800, 20, 20)
threshold = gc.get_threshold()
print("各世代的阈值2:", threshold)
在执行分代回收的时候,程序会被暂停;就譬如说你嗑瓜子,是将瓜子扔在地上的,你妈妈正在扫地,假如你一直嗑瓜子,你妈妈就一直会扫不干净。为了减少程序暂停时间,就采用分代回收去收集清理垃圾,来减少耗时。
4、什么是阈值?
Python分代回收中的阈值是指两个用于判断对象是否应该被垃圾回收的阈值:第一个是每代的阈值,第二个是总体的阈值。
每代的阈值指的是每个分代的垃圾回收阶段执行的次数。每当达到每代的阈值时,Python会触发分代垃圾回收。
总体的阈值则是用于在分代回收期间控制是否触发全局垃圾回收(即回收所有代)的阈值。当某个分代的垃圾回收次数达到总体的阈值时,Python会触发全局垃圾回收。
这两个阈值都可以通过gc.get_threshold()函数获取,它返回一个三元组 (gen0, gen1, gen2),其中 gen0 为每代的阈值, gen1 为总体的阈值。