垃圾回收第二部分
GC垃圾回收(garbage collection)
下面是c的垃圾回收机制:
python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅助的策略。
引用计数机制:
python里面每一个东西,都是对象,他们的核心就是一个结构体:pyobject
typedef struct_object{
int ob_refcnt:
struct_typeobject *ob_type;
}Pyobject
引用计数优点:
1.简单
2.实时性:一旦没有引用,内存就直接释放了,不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数的缺点:
1.维护引用计数消耗资源
2.循环引用
list1= []
list2=[]
list1.append(list2)
list2.append(list1)
list1和list2互相引用,如果不存在其他对象对他们的引用,list1和list2的引用计数也仍然为1,所占内存永远没有办法回收,这将是致命的。对于入今天强大的硬件,缺点一尚且可以接受,但是循环引用导致的内存泄露,注定python还将引入新的回收机制。(标记清除,分代回收)
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def f2():
while True:
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t=c1
del c1
del c2
gc.disable()
f2()
上图是在定义完之后的函数的指向
上图是在执行完del函数之后的样子,由于引用计数不为0,所以内存一直不能得到释放,之后每次重复之后,python执行的内存都会一直接增大,导致占用内存一直增加。
在python里面动态的垃圾回收展示.
Ruby的垃圾清除
Ruby的垃圾回收机制:在Ruby程序创建之前就创建了成百上千个对象,并且将他们串联在链表上,称:可用列表。
想像一下每个白色方块上都标写了“未使用预创建对象”,当我么调用Node.new时,Ruby只需要取一个预创建对象给我们就可以了:
此图是对Ruby语法的简化,实际Ruby语法更为复杂,但是根本没变
再次创建对象
以此类推,Ruby内的垃圾越来越多
之后再将使用的内存标记出来通过0,1
然后再将标记为0的内存释放掉:
之后Ruby将无用的空列表返回到可用列表里面:
上面就是Ruby的垃圾清除。
python里的垃圾清除:
和Ruby不同的是,python不会在程序一开始就申请大的空内存,而是在需要时申请,每次创建每次申请,所以Ruby将申请的时间花在了程序运行开始的申请内存上,而python将申请内存的时间平摊。
此时内存0的地址没有指向,此时引用计数器为0
,程序会将内存释放。
总结:Ruby只有在已经分配的内存满了之后才执行垃圾清除的,python是在没有引用计数之后立刻将垃圾清除的。
python解决循环引用:
此时创建了两个不同了类实例:
之后为两个实例添加了两个属性:
减少一次引用计数:
隔代回收机制(Generation GC算法)
python使用一种不同的链表来持续追踪活跃对象,而不将其称为‘活跃列表’,python内部的c代码将其称为0代(generation zero),每次当你创建一个对象时,python都会将其加入0代里面
再次创建对象:
当出现循环引用时:
当去掉外部对他们的引用时,他们的引用计数器会减少1,但是由于他们相互引用所以,不会为零。
python在的0代长度达到一定时,会对链表上的所有对象的引用计数减1。
此时又会创建一条链表用来保存第一次引用计数不为0的对象,对并且将其移动到第二条链表上,此时0代链表集体为零,用来保存再次新创建的对象,1代链表用来保存经历过一次清理的对象,当2代链表经历过几次清理后,又会将其保存到3代链表里面,三代链表保存的是经历多次清理的对象较为稳定的对象
上面的是我简化之后的,真实的python的GC机制是在将引用计数减1,之前会检测两个对象之间是否有相互引用,然后再减1,而且只是检测到有循环引用的才减少,然后再判断,但是大致方法是上面描述的,只是简化了。
不同的python的版本的GC模块使用方法不同。
python中的GC阈值
在GC模块里面有一个gc.set_debug(flags)方法,用来设置gc的debug日志,一般设置为gc.DEBUG_LEAK
gc.collect([generation])是显示进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表只检查第一二代的对象,2代表检查1,2,3代的对象,如果不传参数,也就是等于传2,默认全部检查,返回不可达参数,默认全部检查。
gc.get_threshold()获取的是gc模块里面自动执行垃圾回收的频率。get.set_threshold(threshold0[,threshold1[,threshold2]])设置自动执行垃圾回收的频率。
gc.get_count()获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表。
如图最后一行命令,700表示什么情况下去清理0代链表,10表示什么情况下去清理0,1代列表,最后一个10表示什么情况下,三代列表一起清理。
700指的是新创建的对象减去释放完的对象的个数,如果大于700,如:新创建了1w个对象,释放了5k个,现在相当于新创建了5k个对象,此时大于700,10的意思是每清理10次0代列表,将清理一次1代列表和0代列表,以此类推,最后一个10是每清理10次1代列表清理三代列表一次。
如图此时第0代经历了清理。
如图所示每经历一次清理,第一代的清理次数加一,加到10,就会对0,1代列表清理一次。
垃圾回收的触发机制
查看一个对象的引用计数
import sys
a ='hello world'
sys.getrefcount(a)
#此时可以查看a对象的引用计数
垃圾回收实例:
import gc
class Class():
def __init__(self):
print('object born ,id %s'%str(hex(id(self)))
def f3():
print('---0---')
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t =c1
print('----1-----')
del c1
del c2
print('----2---')
print(gc.garbage)
print('---3---')
print(gc.collect())#显示执行垃圾回收
print('----4---')
print(gc.garbage)
#打出来清理的垃圾
print('---5---')
if __name__=="__main__":
gc.set_debug(gc.DEBUG_LEAK)#设置gc模块日志
f3()
垃圾清理的本质就是调用模块的__del__方法,所以一般来说不要再类里面进行__del__方法的重写和修改,一旦修改了默认的__del__方法,就会导致之后的循环引用不能够删除,这也是GC方法的bug,所以一般再写类的时候,不修改类的__del__方法。