总结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装饰器验证配置文件示例
Feb 24 Python
python实现基本进制转换的方法
Jul 11 Python
Python 中pandas.read_excel详细介绍
Jun 23 Python
Python数据类型中的“冒号“[::]——分片与步长操作示例
Jan 24 Python
Django视图和URL配置详解
Jan 31 Python
小白如何入门Python? 制作一个网站为例
Mar 06 Python
PyCharm代码回滚,恢复历史版本的解决方法
Oct 22 Python
Python子类继承父类构造函数详解
Feb 19 Python
Python3.5内置模块之random模块用法实例分析
Apr 26 Python
python使用tomorrow实现多线程的例子
Jul 20 Python
Python如何转换字符串大小写
Jun 04 Python
Django模型验证器介绍与源码分析
Sep 08 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的一个完整SMTP类(解决邮件服务器需要验证时的问题)
2006/10/09 PHP
PHP入门
2006/10/09 PHP
php 生成WML页面方法详解
2009/08/09 PHP
探讨:如何使用PhpDocumentor生成文档
2013/06/25 PHP
thinkPHP连接sqlite3数据库的实现方法(附Thinkphp代码生成器下载)
2016/05/27 PHP
php使用高斯算法实现图片的模糊处理功能示例
2016/11/11 PHP
PHP实现可精确验证身份证号码的工具类示例
2018/05/31 PHP
js控制的回到页面顶端goTop的代码实现
2013/03/20 Javascript
jQuery读取XML文件内容的方法
2015/03/09 Javascript
Vuejs第十二篇之动态组件全面解析
2016/09/09 Javascript
使用Require.js封装原生js轮播图的实现代码
2017/06/15 Javascript
AngularJS 教程及实例代码
2017/10/23 Javascript
微信小程序使用progress组件实现显示进度功能【附源码下载】
2017/12/12 Javascript
JSON数据中存在单个转义字符“\”的处理方法
2018/07/11 Javascript
详解VUE里子组件如何获取父组件动态变化的值
2018/12/26 Javascript
浅谈对于react-thunk中间件的简单理解
2019/05/01 Javascript
js图数据结构处理 迪杰斯特拉算法代码实例
2019/09/11 Javascript
Vue实现腾讯云点播视频上传功能的实现代码
2020/08/17 Javascript
在antd Table中插入可编辑的单元格实例
2020/10/28 Javascript
[06:16]《DAC最前线》之地区预选赛全面回顾
2015/01/19 DOTA
Python中查看文件名和文件路径
2017/03/31 Python
Python基于回溯法子集树模板解决旅行商问题(TSP)实例
2017/09/05 Python
python3.6中@property装饰器的使用方法示例
2019/08/17 Python
wxpython绘制圆角窗体
2019/11/18 Python
Python OpenCV读取显示视频的方法示例
2020/02/20 Python
CSS3 渐变(Gradients)之CSS3 径向渐变
2016/07/08 HTML / CSS
电气自动化自荐信
2013/10/10 职场文书
植树节标语
2014/06/27 职场文书
档案管理员岗位职责
2015/02/12 职场文书
学期个人自我总结
2015/02/13 职场文书
幼儿园食品安全责任书
2015/05/08 职场文书
《世界多美呀》教学反思
2016/02/22 职场文书
2016大学生优秀志愿者事迹材料
2016/02/25 职场文书
2016年村党支部公开承诺书
2016/03/24 职场文书
Java实现多文件上传功能
2021/06/30 Java/Android
Docker部署Mysql8的实现步骤
2022/07/07 Servers