python 自带内存回收机制,但时不时也会发生内存泄漏的问题,本文记录 Python 内存泄漏相关内容。
程序运行时都需要在内存中申请资源用于存放变量,python 在处理内存中的变量时会调用垃圾回收机制,会留心那些永远不会被引用的变量并及时回收变量,删除并释放相关资源。
引用记数器
,这是 Python 垃圾回收机制的基础,如果一个对象的引用数量不为 0 那么是不会被垃圾回收的;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
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 内置可变容器中导致的,比较容易排查,一种更加隐蔽的情况为循环引用
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
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
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 已经被删除。
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 = []
pass
-->
对象 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
weakref.WeakValueDictionary
来实现,它的接口跟普通字典完全一样。这样我们不用再自行维护弱引用对象,代码逻辑更加简洁明了import mtutils
import numpy as np
import weakref
dict_leak = weakref.WeakValueDictionary()
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
gc 模块是Python的垃圾收集器模块,gc 使用标记清除算法回收垃圾
import gc
# 强制进行垃圾回收
gc.collect()