理解Python垃圾回收机制


Posted in Python onFebruary 12, 2016

一.垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。

#encoding=utf-8
__author__ = 'kevinlu1010@qq.com'

class ClassA():
  def __init__(self):
    print 'object born,id:%s'%str(hex(id(self)))
  def __del__(self):
    print 'object del,id:%s'%str(hex(id(self)))

def f1():
  while True:
    c1=ClassA()
    del c1

执行f1()会循环输出这样的结果,而且进程占用的内存基本不会变动

object born,id:0x237cf58
object del,id:0x237cf58

c1=ClassA()会创建一个对象,放在0x237cf58内存中,c1变量指向这个内存,这时候这个内存的引用计数是1
del c1后,c1变量不再指向0x237cf58内存,所以这块内存的引用计数减一,等于0,所以就销毁了这个对象,然后释放内存。

1、导致引用计数+1的情况

  • 对象被创建,例如a=23
  • 对象被引用,例如b=a
  • 对象被作为参数,传入到一个函数中,例如func(a)
  • 对象作为一个元素,存储在容器中,例如list1=[a,a]

2、导致引用计数-1的情况

  • 对象的别名被显式销毁,例如del a
  • 对象的别名被赋予新的对象,例如a=24
  • 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
  • 对象所在的容器被销毁,或从容器中删除对象

demo

def func(c,d):
  print 'in func function', sys.getrefcount(c) - 1


print 'init', sys.getrefcount(11) - 1
a = 11
print 'after a=11', sys.getrefcount(11) - 1
b = a
print 'after b=1', sys.getrefcount(11) - 1
func(11)
print 'after func(a)', sys.getrefcount(11) - 1
list1 = [a, 12, 14]
print 'after list1=[a,12,14]', sys.getrefcount(11) - 1
a=12
print 'after a=12', sys.getrefcount(11) - 1
del a
print 'after del a', sys.getrefcount(11) - 1
del b
print 'after del b', sys.getrefcount(11) - 1
# list1.pop(0)
# print 'after pop list1',sys.getrefcount(11)-1
del list1
print 'after del list1', sys.getrefcount(11) - 1

输出

init 24
after a=11 25
after b=1 26
in func function 28
after func(a) 26
after list1=[a,12,14] 27
after a=12 26
after del a 26
after del b 25
after del list1 24

问题:为什么调用函数会令引用计数+2

3、查看一个对象的引用计数

sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1

二.循环引用导致内存泄露

def f2():
  while True:
    c1=ClassA()
    c2=ClassA()
    c1.t=c2
    c2.t=c1
    del c1
    del c2

执行f2(),进程占用的内存会不断增大。

object born,id:0x237cf30
object born,id:0x237cf58

创建了c1,c2后,0x237cf30(c1对应的内存,记为内存1),0x237cf58(c2对应的内存,记为内存2)这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.
在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

三.垃圾回收

deff3():
  # print gc.collect()
  c1=ClassA()
  c2=ClassA()
  c1.t=c2
  c2.t=c1
  del c1
  del c2
  print gc.garbage
  print gc.collect() #显式执行垃圾回收
  print gc.garbage
  time.sleep(10)
if __name__ == '__main__':
  gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
  f3()

输出:

gc: uncollectable <ClassA instance at 0230E918>
gc: uncollectable <ClassA instance at 0230E940>
gc: uncollectable <dict 0230B810>
gc: uncollectable <dict 02301ED0>
object born,id:0x230e918
object born,id:0x230e940
4
  • 垃圾回收后的对象会放在gc.garbage列表里面
  • gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict
  • 有三种情况会触发垃圾回收:

1.调用gc.collect(),
2.当gc模块的计数器达到阀值的时候。
3.程序退出的时候
四.gc模块常用功能解析
gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。

常用函数:
1、gc.set_debug(flags)
设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2、gc.collect([generation])
显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。
返回不可达(unreachable objects)对象的数目
3、gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率。
4、gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

5、gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。

gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:

print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)

3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。

gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10)
每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):

  • 当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
  • 当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
  • 当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)

其他
如果循环引用中,两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,所以为了安全起见,gc模块会把对象放到gc.garbage中,但是不会销毁对象。
五.应用

  • 项目中避免循环引用
  • 引入gc模块,启动gc模块的自动清理循环引用的对象机制
  • 由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
  • gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局

以上就是本文的全部内容。希望对大家的学习有所帮助。

Python 相关文章推荐
Python列表推导式的使用方法
Nov 21 Python
python实现dnspod自动更新dns解析的方法
Feb 14 Python
简单理解Python中的装饰器
Jul 31 Python
itchat和matplotlib的结合使用爬取微信信息的实例
Aug 25 Python
python实现时间o(1)的最小栈的实例代码
Jul 23 Python
详解Python requests 超时和重试的方法
Dec 18 Python
Django项目之Elasticsearch搜索引擎的实例
Aug 21 Python
Python进阶之迭代器与迭代器切片教程
Jan 29 Python
Python random库使用方法及异常处理方案
Mar 02 Python
Django 删除upload_to文件的步骤
Mar 30 Python
python+selenium+chrome批量文件下载并自动创建文件夹实例
Apr 27 Python
解决python存数据库速度太慢的问题
Apr 23 Python
一步步解析Python斗牛游戏的概率
Feb 12 #Python
常用python编程模板汇总
Feb 12 #Python
python黑魔法之参数传递
Feb 12 #Python
python实现井字棋游戏
Mar 30 #Python
python搭建微信公众平台
Feb 09 #Python
Python实例一个类背后发生了什么
Feb 09 #Python
Python中的条件判断语句基础学习教程
Feb 07 #Python
You might like
PHP的宝库目录--PEAR
2006/10/09 PHP
建立文件交换功能的脚本(三)
2006/10/09 PHP
PHP垃圾回收机制简单说明
2010/07/22 PHP
php从给定url获取文件扩展名的方法
2015/03/14 PHP
yii2中添加验证码的实现方法
2016/01/09 PHP
ExtJs使用IFrame的实现代码
2010/03/24 Javascript
JS动态添加与删除select中的Option对象(示例代码)
2013/12/20 Javascript
使用POST方式弹出窗口的两种方法示例介绍
2014/01/29 Javascript
动态创建script在IE中缓存js文件时导致编码的解决方法
2014/05/04 Javascript
JavaScript验证18位身份证号码最后一位正确性的实现代码
2014/08/07 Javascript
实现前后端数据交互方法汇总
2015/04/07 Javascript
Angularjs全局变量被作用域监听的正确姿势
2016/02/06 Javascript
JavaScript初学者必看“new”
2017/06/12 Javascript
用js屏蔽被http劫持的浮动广告实现方法
2017/08/10 Javascript
手把手教你vue-cli单页到多页应用的方法
2018/05/31 Javascript
详解如何在vue-cli中使用vuex
2018/08/07 Javascript
详解element-ui动态限定的日期范围选择器代码片段
2020/07/03 Javascript
Nuxt的动态路由和参数校验操作
2020/11/09 Javascript
Django1.7+python 2.78+pycharm配置mysql数据库教程
2014/11/18 Python
Python lxml模块安装教程
2015/06/02 Python
python+pandas生成指定日期和重采样的方法
2018/04/11 Python
tensorflow 获取模型所有参数总和数量的方法
2018/06/14 Python
python绘制多个曲线的折线图
2020/03/23 Python
Python计算时间间隔(精确到微妙)的代码实例
2019/02/26 Python
Python实现图像去噪方式(中值去噪和均值去噪)
2019/12/18 Python
python中with用法讲解
2020/02/07 Python
CSS3制作炫酷带方向感应的鼠标滑过图片3D动画
2016/03/16 HTML / CSS
深入解析HTML5的IndexedDB索引数据库
2015/09/14 HTML / CSS
西班牙创意礼品和小工具网上商店:Curiosite
2016/07/26 全球购物
小学生红领巾广播稿
2014/01/21 职场文书
员工三分钟演讲稿
2014/08/19 职场文书
教育实践活动对照检查材料
2014/09/23 职场文书
党的作风建设心得体会
2014/10/22 职场文书
2016年优秀党员教师先进事迹材料
2016/02/29 职场文书
Mysql中 unique列插入重复值该怎么解决呢
2021/05/26 MySQL
ORACLE数据库应用开发的三十个注意事项
2021/06/07 Oracle