总结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字符串和字典相关操作的实例详解
Sep 23 Python
纯python实现机器学习之kNN算法示例
Mar 01 Python
django中的HTML控件及参数传递方法
Mar 20 Python
Python爬虫框架Scrapy基本用法入门教程
Jul 26 Python
python 读取鼠标点击坐标的实例
Dec 29 Python
Django rest framework jwt的使用方法详解
Aug 08 Python
python实现比对美团接口返回数据和本地mongo数据是否一致示例
Aug 09 Python
Numpy之reshape()使用详解
Dec 26 Python
python with语句的原理与用法详解
Mar 30 Python
Python @property及getter setter原理详解
Mar 31 Python
python os模块常用的29种方法使用详解
Jun 02 Python
Python extract及contains方法代码实例
Sep 11 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数据记录代码
2008/06/07 PHP
利用php递归实现无限分类 格式化数组的详解
2013/06/08 PHP
ThinkPHP的常用配置选项汇总
2016/03/24 PHP
thinkphp3.2实现上传图片的控制器方法
2016/04/28 PHP
php实时倒计时功能实现方法详解
2017/02/27 PHP
jquery遍历之parent()和parents()的区别及parentsUntil()方法详解
2013/12/02 Javascript
jQuery控制的不同方向的滑动(向左、向右滑动等)
2014/07/18 Javascript
javascript为按钮注册回车事件(设置默认按钮)的方法
2015/05/09 Javascript
Js制作点击输入框时默认文字消失的效果
2015/09/05 Javascript
javascript 继承学习心得总结
2016/03/17 Javascript
js简单时间比较的方法
2016/08/02 Javascript
javascript 数组去重复(在线去重工具)
2016/12/17 Javascript
JS中Array数组学习总结
2017/01/18 Javascript
微信小程序手势操作之单触摸点与多触摸点
2017/03/10 Javascript
jQuery事件对象的属性和方法详解
2017/09/09 jQuery
shiro授权的实现原理
2017/09/21 Javascript
JS实现的简单四则运算计算器功能示例
2017/09/27 Javascript
利用原生js实现html5小游戏之打砖块(附源码)
2018/01/03 Javascript
如何使用electron-builder及electron-updater给项目配置自动更新
2018/12/24 Javascript
Nuxt.js的路由跳转操作(页面跳转nuxt-link)
2020/11/06 Javascript
[01:52]DOTA2完美大师赛Vega战队趣味视频——kpii老师小课堂
2017/11/25 DOTA
Python中logging模块的用法实例
2014/09/29 Python
python基于BeautifulSoup实现抓取网页指定内容的方法
2015/07/09 Python
Python3.5局部变量与全局变量作用域实例分析
2019/04/30 Python
Python完成毫秒级抢淘宝大单功能
2019/06/06 Python
python关于调用函数外的变量实例
2019/12/26 Python
nginx搭建基于python的web环境的实现步骤
2020/01/03 Python
Feelunique美国:欧洲大型的在线美妆零售电商
2018/11/04 全球购物
信号量和自旋锁的区别?如何选择使用?
2015/09/08 面试题
4s客服专员岗位职责
2013/12/01 职场文书
大学校园生活自我鉴定
2014/01/13 职场文书
实用的简历自我评价
2014/03/06 职场文书
2015年组织部工作总结
2015/04/03 职场文书
我的生日感言
2015/08/03 职场文书
mysql 联合索引生效的条件及索引失效的条件
2021/11/20 MySQL
JavaScript实现两个数组的交集
2022/03/25 Javascript