相关文章推荐
长情的火锅  ·  python读取excel,获得下拉选中所有 ...·  3 周前    · 
勤奋的鸭蛋  ·  python - Set up of ...·  2 周前    · 
大力的长颈鹿  ·  python - Conda env ...·  2 周前    · 
讲道义的闹钟  ·  如何释放Python占用的内存?开发者社区·  昨天    · 
聪明的橙子  ·  python内存机制与垃圾回收、调优手段 ...·  昨天    · 
飘逸的热带鱼  ·  .net framework ...·  1 年前    · 
暗恋学妹的投影仪  ·  基元类型、引用 类型与值类型 - CSDN文库·  1 年前    · 
飘逸的山羊  ·  vba打开超链接的文件-掘金·  2 年前    · 
发财的楼房  ·  WPF遍历CheckBox控件实现全选及反选 ...·  2 年前    · 
小胡子的单杠  ·  ORM 实例教程- 阮一峰的网络日志·  2 年前    · 
Code  ›  python 内存泄漏开发者社区
python函数 内存泄漏 python
https://cloud.tencent.com/developer/article/2069006
火星上的小熊猫
2 年前
作者头像
为为为什么
0 篇文章

python 内存泄漏

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 又见苍岚 > python 内存泄漏

python 内存泄漏

作者头像
为为为什么
发布 于 2022-08-09 17:17:50
1.1K 0
发布 于 2022-08-09 17:17:50
举报

python 自带内存回收机制,但时不时也会发生内存泄漏的问题,本文记录 Python 内存泄漏相关内容。

内存泄漏

程序运行时都需要在内存中申请资源用于存放变量,python 在处理内存中的变量时会调用垃圾回收机制,会留心那些永远不会被引用的变量并及时回收变量,删除并释放相关资源。

  • Python 会为变量维护 引用记数器 ,这是 Python 垃圾回收机制的基础,如果一个对象的引用数量不为 0 那么是不会被垃圾回收的;
  • 因此如果在程序中恰好有方法造成了循环引用或通过某种方式使得引用数量无法降至0,则变量无法被回收, 在批量处理大量任务时内存占用便会不断提升
  • 内存泄漏最直接的现象就是 Python 占用的内存量不断增加,直至内存溢出

问题复现

  • 以全局变量阻止垃圾回收为例:
from time import sleep
import numpy as np
import tqdm
if __name__ == '__main__':
    mem_list = []
    for _ in tqdm.tqdm(range(10)):
        huge_mem = np.random.random([1000, 1000, 100])
        mem_list.append(huge_mem)
        sleep(3)
    pass

  • 类似的泄漏现象即是如此,逐渐吃光内存

调试手段

引用数量

  • 可以通过 sys.getrefcount 函数得到对象引用数量
import sys
import numpy as np
if __name__ == '__main__':
    # 建立对象
    test = {}
    # 默认对象引用数量为 2
    print(sys.getrefcount(test)) 		# 2 
    # 为该对象建立引用
    quo = test
    # 添加引用后,二者引用数量为 3
    print(sys.getrefcount(quo)) 		# 3 
    print(sys.getrefcount(test)) 		# 3 
    # 删除引用
    del quo
    # 删除引用后,引用数变回 2
    print(sys.getrefcount(test))		# 2
    # 重新添加相同的引用
    quo = test
    # 和之前一样,引用数变为 3
    print(sys.getrefcount(quo))			# 3
    print(sys.getrefcount(test))		# 3
    # 创建新对象(空列表) 覆盖原始 test 对象(空字典)
    test = []
    # quo 保持对空字典的引用,新对象仅有自己的引用,因此二者都为 2
    print(sys.getrefcount(quo))			# 2
    print(sys.getrefcount(test))		# 2
    print(test, quo)					# [] {}
    pass

  • 应用数量随着引用情况的变化而变化,可以查看变量的引用数是否清空来调试内存泄漏的情况

objgraph

  • objgraph 是一个用于诊断内存问题的工具,可以通过该工具打印对象数量,以此观察内存变化与对象数量的关系。
  • 安装工具
pip install objgraph 

  • 调试示例
import objgraph
if __name__ == '__main__':
    test_list = []
    for _ in range(10):
        test_list.append([])
        objgraph.show_most_common_types(limit=7)
        print()
function           11180
dict               6615
tuple              4385
wrapper_descriptor 2924
list               2643
weakref            2377
member_descriptor  1950
function           11180
dict               6615
tuple              4398
wrapper_descriptor 2924
list               2644
weakref            2377
member_descriptor  1950
function           11180
dict               6615
tuple              4398
wrapper_descriptor 2924
list               2645
weakref            2377
member_descriptor  1950
function           11180
dict               6615
tuple              4398
wrapper_descriptor 2924
list               2646
weakref            2377
member_descriptor  1950
function           11180
dict               6615
tuple              4398
wrapper_descriptor 2924
list               2647
weakref            2377
member_descriptor  1950
...

示例中不断增加 list 对象,在计数器中可以看到仅 list 对象不断递增

弱引用

  • 影响 Python 垃圾回收的核心问题在于对象引用,而Python 内置了一种特殊的引用,该引用不会增加引用数,可以作为垃圾回收良好的技术
  • 详细介绍移步 Python 弱引用 查看

循环引用

大多数内存爆炸增长都是由于将变量存在python 内置可变容器中导致的,比较容易排查,一种更加隐蔽的情况为循环引用

问题复现

import sys
import numpy as np
class MemLeak:
    def __init__(self, name):
        self.name = name
        self.huge_memory = np.random.random([1000, 1000, 100])
        self.child = None
        self.parent = None
    def __del__(self):
        print(f"对象 {self.name} 已经被删除。")
    def ref_count(self):
        print(f"对象 {self.name} 当前引用数为 {sys.getrefcount(self)}")
if __name__ == '__main__':
    fir = MemLeak('first')
    fir.ref_count()
    fir = []
    fir = MemLeak('first')
    sec = MemLeak('second')
    fir.ref_count()
    sec.ref_count()
    # 循环引用
    fir.child = sec
    sec.parent = fir
    fir.ref_count()
    sec.ref_count()
    del sec
    fir.ref_count()
    del fir
    pass

  • 可以看到,正常情况下,创建的对象被覆盖后,如果引用数归零(line 22),则 python 会自动调用回收机制,并同时清空内存
  • 当出现循环引用时,对象的引用数增加了,即使手动 del 对象该对象在内存中也不会被删除,仅会在 python 程序退出时释放内存,也就是 循环引用导致了内存泄漏

解决方案

  • 我们需要打破循环引用导致的引用数增加,在不改变代码逻辑的情况下,可以将部分 引用转换为弱引用 ,在保证功能不变的前提下打破计数的引用环,使得对象删除时内存得以正确释放
  • 修正代码
import sys
import numpy as np
import weakref
class MemLeak:
    def __init__(self, name):
        self.name = name
        self.huge_memory = np.random.random([1000, 1000, 100])
        self.child = None
        self.parent = None
    def __del__(self):
        print(f"对象 {self.name} 已经被删除。")
    def ref_count(self):
        print(f"对象 {self.name} 当前引用数为 {sys.getrefcount(self)}")
if __name__ == '__main__':
    fir = MemLeak('first')
    fir.ref_count()
    fir = []
    fir = MemLeak('first')
    sec = MemLeak('second')
    fir.ref_count()
    sec.ref_count()
    # 循环引用
    fir.child = weakref.ref(sec)
    sec.parent = fir
    fir.ref_count()
    sec.ref_count()
    del sec
    fir.ref_count()
    del fir
    pass

  • 通过调试过程可以看到,在使用弱引用打破计数引用环后,删除对象可以正常释放内存,避免了之前的内存泄漏
  • 使用弱引用时需要注意,弱引用不计入引用数量,因此如果需要某个变量存在,必须给他一个正经的引用名称,如果直接用弱引用指向创建的对象,该对象会由于引用数为0而在创建后直接被删除
import sys
import numpy as np
import weakref
class MemLeak:
    def __init__(self, name):
        self.name = name
        self.huge_memory = np.random.random([1000, 1000, 100])
        self.child = None
        self.parent = None
    def __del__(self):
        print(f"对象 {self.name} 已经被删除。")
    def ref_count(self):
        print(f"对象 {self.name} 当前引用数为 {sys.getrefcount(self)}")
if __name__ == '__main__':
    weakr = weakref.ref(MemLeak('test'))
对象 test 已经被删除。
  • 然而在实际应用中我们不是很喜欢手动删除所有对象,毕竟不写 C++ 好多年了,是否有方案即解决循环引用难以回收的问题,又可以方便地通过直接覆盖变量的方式方便 python 资源自动回收呢,我在这里做了一个尝试供后人参考
import sys
import numpy as np
import weakref
class MemLeak:
    def __init__(self, name):
        self.name = name
        self.huge_memory = np.random.random([1000, 1000, 100])
        self.child = None
        self.parent = None
    def __del__(self):
        print(f"对象 {self.name} 已经被删除。")
    def ref_count(self):
        print(f"对象 {self.name} 当前引用数为 {sys.getrefcount(self)}")
if __name__ == '__main__':
    fir = MemLeak('first')
    fir.ref_count()
    fir.child = MemLeak('second')
    fir.child.parent = weakref.ref(fir)
    fir.ref_count()
    fir = []
对象 first 当前引用数为 4
对象 first 当前引用数为 4
对象 first 已经被删除。
对象 second 已经被删除。

  • 思路就是根节点中的变量维护其余节点的唯一引用,同时其余节点反向引用时使用 弱引用 ,这样根节点和其他节点都仅有一个有效引用,并且其他节点的引用会随着根节点的消失而清空,这样仅通过覆盖根节点即完成了循环引用中所有变量的销毁回收

字典缓存

问题复现

  • 字典经常用来保存已经生成的变量,避免使用同一个结果的函数多次生成
  • 然而临时结果在无人引用时由于字典的引用会导致保存的对象不会自动释放
import mtutils
import numpy as np
import weakref
dict_leak = {}
if __name__ == '__main__':
    for index in mtutils.tqdm(range(6)):
        key = mtutils.create_uuid()
        value = np.random.random([1000, 1000, 100])
        dict_leak[key] = value

  • 尽管原始创建的变量已经被覆盖销毁,由于在字典中仍保留了他们的引用,因此内存不会被释放

解决方案

  • 解决的思路还是从引用数上入手,我们的需求是令那些不再有人能引用到的 value 被清理回收
  • 实际上,用字典缓存数据对象的做法很常用,为此 weakref 模块还提供了两种只保存弱引用的字典对象
    • weakref.WeakKeyDictionary ,键只保存弱引用的映射类(一旦键不再有强引用,键值对条目将自动消失);
    • weakref.WeakValueDictionary ,值只保存弱引用的映射类(一旦值不再有强引用,键值对条目将自动消失);
  • 因此,我们的数据缓存字典可以采用 weakref.WeakValueDictionary 来实现,它的接口跟普通字典完全一样。这样我们不用再自行维护弱引用对象,代码逻辑更加简洁明了
import mtutils
import numpy as np
import weakref
dict_leak = weakref.WeakValueDictionary()
if __name__ == '__main__':
    for index in mtutils.tqdm(range(6)):
 
推荐文章
长情的火锅  ·  python读取excel,获得下拉选中所有选项_python获取下拉菜单内容 excel
3 周前
勤奋的鸭蛋  ·  python - Set up of virtual environment in anaconda failing - Stack Overflow
2 周前
大力的长颈鹿  ·  python - Conda env create from .yml gives "unexpected error" - Stack Overflow
2 周前
讲道义的闹钟  ·  如何释放Python占用的内存?开发者社区
昨天
聪明的橙子  ·  python内存机制与垃圾回收、调优手段 - 长安223
昨天
飘逸的热带鱼  ·  .net framework 调用目标发生异常 - CSDN文库
1 年前
暗恋学妹的投影仪  ·  基元类型、引用 类型与值类型 - CSDN文库
1 年前
飘逸的山羊  ·  vba打开超链接的文件-掘金
2 年前
发财的楼房  ·  WPF遍历CheckBox控件实现全选及反选_wpf 全选_路过的程序猿的博客-CSDN博客
2 年前
小胡子的单杠  ·  ORM 实例教程- 阮一峰的网络日志
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号