浅谈Python的垃圾回收机制


Posted in Python onDecember 17, 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的情况
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
导致引用计数-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

查看一个对象的引用计数
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()

输出:

Python

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模块常用功能解析

Garbage Collector interface

gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。

常用函数:

gc.set_debug(flags)
设置gc的debug日志,一般设置为gc.DEBUG_LEAK

gc.collect([generation])
显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。
返回不可达(unreachable objects)对象的数目

gc.set_threshold(threshold0[, threshold1[, threshold2])
设置自动执行垃圾回收的频率。

gc.get_count()
获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表

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将xml和xsl转换为html的方法
Mar 10 Python
python类和继承用法实例
Jul 07 Python
python利用matplotlib库绘制饼图的方法示例
Dec 18 Python
Python SqlAlchemy动态添加数据表字段实例解析
Feb 07 Python
python os用法总结
Jun 08 Python
python爬虫爬取微博评论案例详解
Mar 27 Python
python自动化测试之如何解析excel文件
Jun 27 Python
基于python全局设置id 自动化测试元素定位过程解析
Sep 04 Python
python KNN算法实现鸢尾花数据集分类
Oct 24 Python
基于Django实现日志记录报错信息
Dec 17 Python
TensorFlow tf.nn.conv2d_transpose是怎样实现反卷积的
Apr 20 Python
python新手学习可变和不可变对象
Jun 11 Python
谈谈如何手动释放Python的内存
Dec 17 #Python
深入理解NumPy简明教程---数组3(组合)
Dec 17 #Python
深入理解NumPy简明教程---数组2
Dec 17 #Python
深入理解NumPy简明教程---数组1
Dec 17 #Python
Python脚本获取操作系统版本信息
Dec 17 #Python
详解python中xlrd包的安装与处理Excel表格
Dec 16 #Python
详解python开发环境搭建
Dec 16 #Python
You might like
windows服务器中检测PHP SSL是否开启以及开启SSL的方法
2014/04/25 PHP
学习php设计模式 php实现单例模式(singleton)
2015/12/07 PHP
php while循环控制的简单实例
2016/05/30 PHP
Yii2 RESTful中api的使用及开发实例详解
2016/07/06 PHP
php微信公众号开发之二级菜单
2018/10/20 PHP
jQuery boxy弹出层插件中文演示及使用讲解
2011/02/24 Javascript
获取内联和链接中的样式(js代码)
2013/04/11 Javascript
json数据与字符串的相互转化示例
2013/09/18 Javascript
javascript相关事件的几个概念
2015/05/21 Javascript
JavaScript变量的作用域全解析
2015/08/14 Javascript
jquery实现动画菜单的左右滚动、渐变及图形背景滚动等效果
2015/08/25 Javascript
javascript html5 canvas实现可拖动省份的中国地图
2016/03/11 Javascript
Jquery通过ajax请求NodeJS返回json数据实例
2016/11/08 NodeJs
纯JavaScript实现实时反馈系统时间
2017/10/26 Javascript
vue-cli 引入、配置axios的方法
2018/05/08 Javascript
angular5 子组件监听父组件传入值的变化方法
2018/09/30 Javascript
详解微信小程序图片地扯转base64解决方案
2019/08/18 Javascript
H5 js点击按钮复制文本到粘贴板
2020/11/19 Javascript
python实现巡检系统(solaris)示例
2014/04/02 Python
Python中replace方法实例分析
2014/08/20 Python
快速解决vue.js 模板和jinja 模板冲突的问题
2019/07/26 Python
python自动保存百度盘资源到百度盘中的实例代码
2019/08/26 Python
在PyCharm中遇到pip安装 失败问题及解决方案(pip失效时的解决方案)
2020/03/10 Python
python中如何写类
2020/06/29 Python
Python的控制结构之For、While、If循环问题
2020/06/30 Python
anaconda安装pytorch1.7.1和torchvision0.8.2的方法(亲测可用)
2021/02/01 Python
让IE6支持css3,让 IE7、IE8 都支持CSS3
2011/10/09 HTML / CSS
东南亚地区最大的购物网站Lazada新加坡站点:Lazada.sg
2016/07/17 全球购物
美国知名的女性服饰品牌:LOFT(洛芙特)
2016/08/05 全球购物
JD Sports西班牙:英国领先的运动服装公司
2020/01/06 全球购物
竞选班委演讲稿
2014/04/28 职场文书
关于对大人不礼貌的检讨书
2014/09/29 职场文书
增值税发票丢失证明
2015/06/19 职场文书
转学证明范本
2015/06/19 职场文书
导游词之任弼时故居
2020/01/07 职场文书
python神经网络Xception模型
2022/05/06 Python