总结python 三种常见的内存泄漏场景


Posted in Python onNovember 20, 2020

概要

不要以为 Python 有自动垃圾回收就不会内存泄漏,本着它有“垃圾回收”我有“垃圾代码”的精神,现在总结一下三种常见的内存泄漏场景。

无穷大导致内存泄漏

如果把内存泄漏定义成只申请不释放,那么借着 Python 中整数可以无穷大的这个特点,我们一行代码就可以完成内存泄漏了。

i = 1024 ** 1024 ** 1024

循环引用导致内存泄漏

引用记数器 是 Python 垃圾回收机制的基础,如果一个对象的引用数量不为 0 那么是不会被垃圾回收的,我们可以通过 sys.getrefcount 来得到给定对象的引用数量。

In [1]: import sys                               

In [2]: a = {'name':'tom','age':16}                       

In [3]: sys.getrefcount(a)  # 由于 getrefcount 内部也会临时的引用 a 所以,使得计数器的值变成了 2 。               
Out[3]: 2

In [4]: b = a                                  

In [5]: sys.getrefcount(a)                           
Out[5]: 3

先来看一个循环引用的场景。

#!/usr/bin/evn python3

import sys
import time
import threading


class Person(object):
  free_lock = threading.Condition()

  def __init__(self, name: str = ""):
    """
    Parameters
    ----------
    name: str
      姓名

    best_friend: str
      最要好的朋友名
    """
    self._name = name
    self._best_friend = None

  @property
  def best_friend(self, person: "Person"):
    return self._best_friend

  @best_friend.setter
  def best_friend(self, friend: "Person"):
    self._best_friend = friend

  def __str__(self):
    """
    """
    return self._name

  def __del__(self):
    """
    """
    self.free_lock.acquire()
    print(f"{self._name} 要 GG 了,现在释放它的内存空间。")
    sys.stderr.flush()
    self.free_lock.release()


def mem_leak():
  """
  循环引用导致内存泄漏
  """
  zhang_san = Person(name='张三')
  li_si = Person("李四")

  # 构造出循环引用
  # 李四的好友是张三
  li_si.best_friend = zhang_san
  # 张三的好友是李四
  zhang_san.best_friend = li_si


if __name__ == "__main__":
  for i in range(3):
    time.sleep(0.01)
    print(f"{i}")
    mem_leak()

  print("mem_leak 执行完成了.")
  time.sleep(5)

运行效果。

python3 main.py
0
1
2
mem_leak 执行完成了.
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间。
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间。
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间

由于循环引用的存在,使得 mem_leak 函数就行执行完了其内部的局部变量引用计数器也不为 0 ,所以内存得不到及时的释放。释放这部分内存有两个途径 1、 被 Python 内部的循环检测机制发现了; 2、进程退出前的集中释放。

tracemalloc 可以在一定程序上帮我们发现问题,在此就不讲怎么用了,我们直接上解决方案。Python 为程序员提供了弱引用,通过这种方式可以不增加对象引用计数器的数值,这成为了我们打破循环引用的一种手段。

In [1]: import sys                               

In [2]: import weakref                             

In [3]: from main import Person                         

In [4]: tom = Person('tom')                           

In [5]: sys.getrefcount(tom)                          
Out[5]: 2

In [6]: p = weakref.ref(tom)                          

In [7]: sys.getrefcount(tom)  # 弱引用不会增加计数器的值                        
Out[7]: 2

现在使用 weakref 技术来改造我们的代码。

#!/usr/bin/evn python3


import sys
import time
import weakref
import threading


class Person(object):
  free_lock = threading.Condition()

  def __init__(self, name: str = ""):
    """
    Parameters
    ----------
    name: str
      姓名

    best_friend: str
      最要好的朋友名
    """
    self._name = name
    self._best_friend = None

  @property
  def best_friend(self, person: "Person"):
    return self._best_friend

  @best_friend.setter
  def best_friend(self, friend: "Person"):
    self._best_friend = weakref.ref(friend)

  def __str__(self):
    """
    """
    return self._name

  def __del__(self):
    """
    """
    self.free_lock.acquire()
    print(f"{self._name} 要 GG 了,现在释放它的内存空间。")
    sys.stderr.flush()
    self.free_lock.release()


def mem_leak():
  """
  循环引用导致内存泄漏
  """
  zhang_san = Person(name='张三')
  li_si = Person("李四")

  # 构造出循环引用
  # 李四的好友是张三
  li_si.best_friend = zhang_san
  # 张三的好友是李四
  zhang_san.best_friend = li_si


if __name__ == "__main__":
  for i in range(3):
    time.sleep(0.01)
    print(f"{i}")
    mem_leak()

  print("mem_leak 执行完成了.")
  time.sleep(5)

运行效果。

python3 main.py
0
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间。
1
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间。
2
张三 要 GG 了,现在释放它的内存空间。
李四 要 GG 了,现在释放它的内存空间。
mem_leak 执行完成了.

可以看到现在一旦函数执行完成,其内部的局部变量的内存就会得到释放,非常的及时。

外面库导致内存泄漏

这种情况我也只遇到过一次,之前 mysql-connector-python 的内存泄漏,导致我的程序跑着跑着占用的内存就越来越大;最后我们返的 C 语言扩展禁用之后就没有问题了。

以上就是总结python 三种常见的内存泄漏场景的详细内容,更多关于python 内存泄漏的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python使用append合并两个数组的方法
Apr 28 Python
Python 装饰器使用详解
Jul 29 Python
python3 pandas 读取MySQL数据和插入的实例
Apr 20 Python
Pycharm设置utf-8自动显示方法
Jan 17 Python
详解python中TCP协议中的粘包问题
Mar 22 Python
python flask框架实现传数据到js的方法分析
Jun 11 Python
opencv3/python 鼠标响应操作详解
Dec 11 Python
在Keras中实现保存和加载权重及模型结构
Jun 15 Python
详解用Python爬虫获取百度企业信用中企业基本信息
Jul 02 Python
python爬虫工具例举说明
Nov 30 Python
超详细Python解释器新手安装教程
May 10 Python
Python中json.dumps()函数的使用解析
May 17 Python
Python偏函数实现原理及应用
Nov 20 #Python
python与idea的集成的实现
Nov 20 #Python
安装pyinstaller遇到的各种问题(小结)
Nov 20 #Python
python3 re返回形式总结
Nov 20 #Python
python 实现图片修复(可用于去水印)
Nov 19 #Python
python 删除系统中的文件(按时间,大小,扩展名)
Nov 19 #Python
Python并发爬虫常用实现方法解析
Nov 19 #Python
You might like
PHP+Mysql+jQuery实现动态展示信息
2011/10/08 PHP
php阻止页面后退的方法分享
2014/02/17 PHP
php 算法之实现相对路径的实例
2017/10/17 PHP
浅析PHP7的多进程及实例源码
2019/04/14 PHP
使用Javascript和DOM Interfaces来处理HTML
2006/10/09 Javascript
JQueryEasyUI datagrid框架的基本使用
2013/04/08 Javascript
httpclient模拟登陆具体实现(使用js设置cookie)
2013/12/11 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
2014/07/10 Javascript
javascript动态控制服务器控件实例
2014/09/05 Javascript
jquery实现html页面 div 假分页有原理有代码
2014/09/06 Javascript
jQuery中animate动画第二次点击事件没反应
2015/05/07 Javascript
jquery用ajax方式从后台获取json数据后如何将内容填充到下拉列表
2015/08/26 Javascript
用JS生成UUID的方法实例
2016/03/30 Javascript
Bootstrap Paginator分页插件使用方法详解
2016/05/30 Javascript
jQuery EasyUI常用数据验证汇总
2016/09/18 Javascript
BootStrap 图标icon符号图标glyphicons不正常显示的快速解决办法
2016/12/08 Javascript
利用vue写todolist单页应用
2016/12/15 Javascript
浅谈原型对象的常用开发模式
2017/07/22 Javascript
jQuery 获取除某指定对象外的其他对象 ( :not() 与.not())
2018/10/10 jQuery
js设计模式之单例模式原理与用法详解
2019/08/15 Javascript
VUE 自定义组件模板的方法详解
2019/08/30 Javascript
Javascript操作select控件代码实例
2020/02/14 Javascript
学习python (2)
2006/10/31 Python
浅谈django中的认证与登录
2016/10/31 Python
Python实现可自定义大小的截屏功能
2018/01/20 Python
分享Python切分字符串的一个不错方法
2018/12/14 Python
Python通用函数实现数组计算的方法
2019/06/13 Python
python通过http下载文件的方法详解
2019/07/26 Python
django重新生成数据库中的某张表方法
2019/08/28 Python
Python列表元素删除和remove()方法详解
2021/01/04 Python
美国最大的半成品净菜电商:Blue Apron(蓝围裙)
2018/04/27 全球购物
留学自荐信写作方法
2014/01/27 职场文书
上课说话检讨书
2015/01/27 职场文书
小区环境卫生倡议书
2015/04/29 职场文书
python中pandas.read_csv()函数的深入讲解
2021/03/29 Python
go 实现简易端口扫描的示例
2021/05/22 Golang