Chinaunix首页 | 论坛 | 博客
  • 博客访问: 303700
  • 博文数量: 21
  • 博客积分: 250
  • 博客等级: 二等列兵
  • 技术积分: 484
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-06 23:10
个人简介

程序猿

文章分类

全部博文(21)

文章存档

2016年(17)

2014年(3)

2013年(1)

分类: Python/Ruby

2016-01-16 15:40:10

Python的垃圾回收机制和其他的一些语言(如java)不同。Java使用跟踪收集器,从根集出发扫描各个对象,可以扫描到的对象不会被释放,其他对象应该被释放。
Python由于一些扩展模块(如C扩展模块)的工作方式,无法找到所有的根集对象,所以不能使用这种垃圾回收机制。

Python采用的方式也挺简单。因为python已经使用了引用计数方式(见上文),引用计数不能解决的是循环引用问题,那回收机制只需要解决这个问题就可以了。
对于循环引用,只有容器对象才会存在该问题,python中的容器对象有list,tuple,dict,class,instances,python的内存管理模块会使用双向链表串联起这些对象,并为它们添加一个新的计数:gc_refs,然后使用以下步骤找出循环引用对象:
1. 设置双向链表中所有对象的gc_refs初始值为其引用计数值
2. 把每个对象中引用的对象的gc_refs值减1
3. 遍历双向链表,移除gc_refs大于1的对象,添加进新的集合中,这些对象的内存不能被释放
4. 遍历集合,在双向链表中找到集合中每个对象引用的对象,并移除,这些对象也不能被释放
5. 双向链表中剩余的对象就是无法访问到的对象,需要被释放

了解了原理之后,我们再来做试验。
我们需要导入python的gc包来操作内存垃圾回收。
  1. >>> import gc
  2. >>> gc.isenabled() #查看gc模块是否打开
  3. True
默认情况下,gc自然是打开的。
如果我们的程序中能保证没有使用循环引用,那么为了提高效率可以调用gc.disable()关闭gc模块。

有两种情况会触发python的垃圾回收。
1. 用户显式调用gc.collect()
来看例子。
  1. >>> import gc
  2. >>> gc.collect()
  3. 0
  4. >>> a = []
  5. >>> a = 3
  6. >>> gc.collect()
  7. 0
gc.collect()函数的返回值是释放对象的个数。执行a = 3的时候,a先前引用的对象“[]”就因为引用计数为0被释放了。gc模块没有释放任何对象,所以gc.collect()返回0

  1. >>> a = []
  2. >>> a.append(a)
  3. >>> del(a)
  4. >>> gc.collect()
  5. 1
这个是典型的循环引用,在这里引用计数失效了,gc.collect()返回1,说明本次垃圾回收回收了一个对象。

2. Python会记录分配对象和释放对象的次数。当两者的差值大于阈值时,进行垃圾回收。
同时,python还采用分代(generation)回收策略。
一般情况下,对象存在时间越久就越不容易变成垃圾。基于这点假设,python将所有的对象分成三代(0代、1代、2代)。新建对象都是0代对象。如果对象经历了一次垃圾回收后依然存活,就变成下一代对象。垃圾回收时,会扫描所有的0代对象,0代对象扫描次数到达一个阈值后,扫描1代对象,1代对象扫描次数到达另一个阈值后,扫描2代对象,如此循环。
以上3个(红色字体的)阈值可以由函数gc.set_threshold(threshold0[, threshold1[, threshold2]])来指定。

使用gc.get_threshold()函数可以获取当前的配置:
  1. >>> gc.get_threshold()
  2. (700, 10, 10)
默认情况下,gc的配置为700,10,10,也就是说,当分配对象次数比释放对象次数大700的时候,开始启动垃圾回收,每10次0代对象回收后会进行1次1代回收,每10次1代回收后会进行1次2代回收。


阅读(3401) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~