粗略分析Python中的内存泄漏


Posted in Python onApril 23, 2015

引子

之前一直盲目的认为 Python 不会存在内存泄露, 但是眼看着上线的项目随着运行时间的增长 而越来越大的内存占用, 我意识到我写的程序在发生内存泄露, 之前 debug 过 logging 模块导致的内存泄露.

目前看来, 还有别的地方引起的内存泄露. 经过一天的奋战, 终于找到了内存泄露的地方, 目前项目 跑了很长时间, 在业务量较小的时候内存还是能回到刚启动的时候的内存占用.
什么情况下不用这么麻烦

如果你的程序只是跑一下就退出大可不必大费周章的去查找是否有内存泄露, 因为 Python 在退出时 会释放它所分配的所有内存, 如果你的程序需要连续跑很长时间那么就要仔细的查找是否 产生了内存泄露.
场景

如何产生的内存泄露呢, 项目是一个 TCP server, 每当有连接过来时都会创建一个连接实例来进行 管理, 每次断开时连接实例还被占用并没有释放. 没有被释放的原因肯定是因为有某个地方对连接 实例的引用没有释放, 所以随着时间的推移, 连接创建分配内存, 连接断开并没有释放掉内存, 所以 就会产生内存泄露.
调试方法

由于不知道具体是哪里引起的内存泄露, 所以要耐心的一点点调试.

由于知道了断开连接时没有释放, 所以我就不停的模拟创建连接然后发送一些包后断开连接, 然后通过下面一行 shell 来观察内存占用情况:

PID=50662;while true; do; ps aux | grep $PID | grep -v grep | awk '{print $5" "$6}' >> t; sleep 1; done

如果在增长了一定的量后保持住就说明已经没有产生泄露.

同时可以在对象该释放的时候查看对象的引用计数, 通过 sys.getrefcount(obj). 如果引用计数变为了 2 则说明该对象在跳出命名空间后就会被正确回收.
产生原因

项目中两种情况导致对象没有被正确回收:

  •     被退出才回收的对象引用
  •     交叉引用

被退出才回收的对象引用

为了追踪连接所以把连接对象同时放在一个列表里, 而这个列表只有在程序退出时才会被回收, 如果不正确处理, 那么分配的对象将也会只在程序退出时才会被回收.

全局变量和类变量都只会在程序退出的时候才会被回收:

_CONNECTIONS = []

# ...
class Connection(object):
 def __init__(self, sock, address)
  pass

def server_loop():
 # ...
 sock, address = server_sock.accept()
 connection = Connection(sock, address)
 _CONNECTIONS.append(connection)
 # ...
 sock.close()

上面把所有建立的连接都放在全局变量 _CONNECTIONS 里, 如果在关闭的时候不从这个列表 里取出(减少引用)则 connection 对象就不会被回收, 则每建立一次连接就会有个连接对象和连接 对象引用的对象不会被回收.

如果把对象放在一个类属性里也是一样的, 因为类对象在程序一开始就分配, 并在程序退出时才被回收.

解决办法就是在退出时从列表(或其他对象)里解除对对象的引用(删除)

_CONNECTIONS = []

# ...
class Connection(object):
 def __init__(self, sock, address)
  pass

def server_loop():
 # ...
 sock, address = server_sock.accept()
 connection = Connection(sock, address)
 _CONNECTIONS.append(connection)
 try:
  # ...
  sock.close()
 finally:
  _CONNECTIONS.remove(connection) # XXX

交叉引用

有时候我们为对象分配一个实例属性时需要将自己本身赋值给实例属性, 作为实例属性的实例属性, 说着很拗口, 看一下代码:

class ConnectionHandler(object):
 def __init__(self, connection):
  self._conn = connection


class Connection(object):
 def __init__(self, sock, address)
  self._conn_handler = ConnectionHandler(self) # XXX

上面的代码就会产生交叉引用, 交叉引用会让解释器困惑, 从而之后只能靠2代和3代回收, 这个过程可能会很慢.

解决这种问题的方法就是使用 弱引用

import weakref

class ConnectionHandler(object):
 def __init__(self, connection):
  self._conn = connection


class Connection(object):
 def __init__(self, sock, address)
  self._conn_handler = ConnectionHandler(weakref.proxy(self)) # XXX
Python 相关文章推荐
python算法学习之基数排序实例
Dec 18 Python
从零学python系列之数据处理编程实例(一)
May 22 Python
Python中的对象,方法,类,实例,函数用法分析
Jan 15 Python
python将秒数转化为时间格式的实例
Sep 16 Python
详解python Todo清单实战
Nov 01 Python
Python 串口读写的实现方法
Jun 12 Python
python3+PyQt5 实现Rich文本的行编辑方法
Jun 17 Python
python 中的列表生成式、生成器表达式、模块导入
Jun 19 Python
Python解决pip install时出现的Could not fetch URL问题
Aug 01 Python
用Cython加速Python到“起飞”(推荐)
Aug 01 Python
python实现在多维数组中挑选符合条件的全部元素
Nov 26 Python
Python:__eq__和__str__函数的使用示例
Sep 26 Python
使用beaker让Facebook的Bottle框架支持session功能
Apr 23 #Python
用Python编写脚本使IE实现代理上网的教程
Apr 23 #Python
在Python的Bottle框架中使用微信API的示例
Apr 23 #Python
最基础的Python的socket编程入门教程
Apr 23 #Python
利用Python实现简单的相似图片搜索的教程
Apr 23 #Python
以911新闻为例演示Python实现数据可视化的教程
Apr 23 #Python
Python中优化NumPy包使用性能的教程
Apr 23 #Python
You might like
海贼王动画变成“真人”后,凯多神还原,雷利太帅了!
2020/04/09 日漫
md5 16位二进制与32位字符串相互转换示例
2013/12/30 PHP
PHP将MySQL的查询结果转换为数组并用where拼接的示例
2016/05/13 PHP
php版微信开发Token验证失败或请求URL超时问题的解决方法
2016/09/23 PHP
脚本安需导入(装载)的三种模式的对比
2007/06/24 Javascript
javascript innerHTML、outerHTML、innerText、outerText的区别
2008/11/24 Javascript
从零学jquery之如何使用回调函数
2014/05/16 Javascript
JavaScript实现检查页面上的广告是否被AdBlock屏蔽了的方法
2014/11/03 Javascript
javascript白色简洁计算器
2015/05/04 Javascript
JavaScript中数据结构与算法(五):经典KMP算法
2015/06/19 Javascript
js实现当复选框选择匿名登录时隐藏登录框效果
2015/08/14 Javascript
jQuery实现自动切换播放的经典滑动门效果
2015/09/12 Javascript
javascript 中关于array的常用方法详解
2017/05/05 Javascript
详解react-redux插件入门
2018/04/19 Javascript
Vue.js 时间转换代码及时间戳转时间字符串
2018/10/16 Javascript
vue强制刷新组件的方法示例
2019/02/28 Javascript
浅谈如何优雅处理JavaScript异步错误
2019/11/12 Javascript
JavaScript实现模态对话框实例
2020/01/13 Javascript
node.js基于dgram数据报模块创建UDP服务器和客户端操作示例
2020/02/12 Javascript
原生JS实现多条件筛选
2020/08/19 Javascript
[02:51]DOTA2英雄基础教程 风暴之灵
2013/12/23 DOTA
[59:42]Secret vs Alliacne 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python正则表达式抓取成语网站
2013/11/20 Python
跟老齐学Python之有容乃大的list(2)
2014/09/15 Python
Python爬取京东的商品分类与链接
2016/08/26 Python
使用PyV8在Python爬虫中执行js代码
2017/02/16 Python
Python用Pillow(PIL)进行简单的图像操作方法
2017/07/07 Python
Python面向对象思想与应用入门教程【类与对象】
2019/04/12 Python
python opencv将表格图片按照表格框线分割和识别
2019/10/30 Python
python 导入数据及作图的实现
2019/12/03 Python
Python加密模块的hashlib,hmac模块使用解析
2020/01/02 Python
无惧面试,带你搞懂python 装饰器
2020/08/17 Python
小学生国庆演讲稿
2014/09/05 职场文书
挂职个人工作总结
2015/03/05 职场文书
Windows 64位 安装 mysql 8.0.28 图文教程
2022/04/19 MySQL
Java实现添加条码或二维码到Word文档
2022/06/01 Java/Android