粗略分析Python中的内存泄漏

 更新时间:2015年04月23日 16:37:41   作者:C Wong  
这篇文章主要介绍了粗略分析Python中的内存泄漏,分析了包括在垃圾回收时产生等的原因,需要的朋友可以参考下

引子

之前一直盲目的认为 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安装Numpy出现异常信息简单解决办法

    Python安装Numpy出现异常信息简单解决办法

    在安装Python的Numpy包时,可能会遇到路径警告或包源超时的问题,首先,如果出现包源超时,可以尝试更换为国内的镜像源,如清华大学镜像源,其次,如果在安装完成后提示将某个路径添加到PATH环境变量,按照提示操作即可消除异常,需要的朋友可以参考下
    2024-09-09
  • Python数据分析之 Matplotlib 3D图详情

    Python数据分析之 Matplotlib 3D图详情

    本文主要介绍了Python数据分析之Matplotlib 3D图详情,Matplotlib提供了mpl_toolkits.mplot3d工具包来进行3D图表的绘制,下文总结了更多相关资料,需要的小伙伴可以参考一下
    2022-05-05
  • 用python生成mysql数据库结构文档

    用python生成mysql数据库结构文档

    大家好,本篇文章主要讲的是用python生成mysql数据库结构文档,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • python中字符串内置函数的用法总结

    python中字符串内置函数的用法总结

    这篇文章给大家总结了python中字符串内置函数的用法以及相关知识点内容,有兴趣的朋友学习下。
    2018-09-09
  • 检测pytorch是否使用GPU的方法小结

    检测pytorch是否使用GPU的方法小结

    pytorch在服务器上跑的很慢,nvidia-smi显示GPU利用率一直是0%,所以本文总结了一些可以检测深度学习算法是否在使用GPU的方法,需要的朋友可以参考下
    2023-12-12
  • Python写的创建文件夹自定义函数mkdir()

    Python写的创建文件夹自定义函数mkdir()

    这篇文章主要介绍了Python写的创建文件夹自定义函数mkdir(),文件夹操作是编程中经常需要的,mkdir函数更是经典中的经典,需要的朋友可以参考下
    2014-08-08
  • 浅析Python如何实现Celery任务队列系统

    浅析Python如何实现Celery任务队列系统

    这篇文章主要为大家详细介绍了一个基于 Celery 和 Redis 的分布式任务队列系统,用于处理异步任务和定时任务,希望对大家有一定的帮助
    2025-04-04
  • Pygame游戏开发之太空射击实战入门篇

    Pygame游戏开发之太空射击实战入门篇

    相信大多数8090后都玩过太空射击游戏,在过去游戏不多的年代太空射击自然属于经典好玩的一款了,今天我们来自己动手实现它,在编写学习中回顾过往展望未来,下面开始入门篇
    2022-08-08
  • Python Numpy中数组的集合操作详解

    Python Numpy中数组的集合操作详解

    这篇文章主要为大家详细介绍了Python Numpy中数组的一些集合操作方法,文中的示例代码讲解详细,对我们学习Python有一定帮助,需要的可以参考一下
    2022-08-08
  • Python中sorted()函数的强大排序技术实例探索

    Python中sorted()函数的强大排序技术实例探索

    排序在编程中是一个基本且重要的操作,而Python的sorted()函数则为我们提供了强大的排序能力,在本篇文章中,我们将深入研究不同排序算法、sorted() 函数的灵活性,以及各种排序场景下的最佳实践
    2024-01-01

最新评论