python垃圾回收part 2

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的垃圾清除

左边是python右边是Ruby
Ruby的垃圾回收机制:在Ruby程序创建之前就创建了成百上千个对象,并且将他们串联在链表上,称:可用列表。
可用列表想像一下每个白色方块上都标写了“未使用预创建对象”,当我么调用Node.new时,Ruby只需要取一个预创建对象给我们就可以了:
Ruby语言
此图是对Ruby语法的简化,实际Ruby语法更为复杂,但是根本没变
在这里插入图片描述
再次创建对象
在这里插入图片描述
在这里插入图片描述以此类推,Ruby内的垃圾越来越多
在这里插入图片描述之后再将使用的内存标记出来通过0,1
在这里插入图片描述然后再将标记为0的内存释放掉:
在这里插入图片描述之后Ruby将无用的空列表返回到可用列表里面:
在这里插入图片描述上面就是Ruby的垃圾清除。

python里的垃圾清除:

和Ruby不同的是,python不会在程序一开始就申请大的空内存,而是在需要时申请,每次创建每次申请,所以Ruby将申请的时间花在了程序运行开始的申请内存上,而python将申请内存的时间平摊。
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__方法。
在这里插入图片描述

上面就是GC的注意点!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值