总结python 三种常见的内存泄漏场景
概要
不要以为Python有自动垃圾回收就不会内存泄漏,本着它有“垃圾回收”我有“垃圾代码”的精神,现在总结一下三种常见的内存泄漏场景。
无穷大导致内存泄漏
如果把内存泄漏定义成只申请不释放,那么借着Python中整数可以无穷大的这个特点,我们一行代码就可以完成内存泄漏了。
i=1024**1024**1024
循环引用导致内存泄漏
引用记数器是Python垃圾回收机制的基础,如果一个对象的引用数量不为0那么是不会被垃圾回收的,我们可以通过sys.getrefcount来得到给定对象的引用数量。
In[1]:importsys
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/evnpython3
importsys
importtime
importthreading
classPerson(object):
free_lock=threading.Condition()
def__init__(self,name:str=""):
"""
Parameters
----------
name:str
姓名
best_friend:str
最要好的朋友名
"""
self._name=name
self._best_friend=None
@property
defbest_friend(self,person:"Person"):
returnself._best_friend
@best_friend.setter
defbest_friend(self,friend:"Person"):
self._best_friend=friend
def__str__(self):
"""
"""
returnself._name
def__del__(self):
"""
"""
self.free_lock.acquire()
print(f"{self._name}要GG了,现在释放它的内存空间。")
sys.stderr.flush()
self.free_lock.release()
defmem_leak():
"""
循环引用导致内存泄漏
"""
zhang_san=Person(name='张三')
li_si=Person("李四")
#构造出循环引用
#李四的好友是张三
li_si.best_friend=zhang_san
#张三的好友是李四
zhang_san.best_friend=li_si
if__name__=="__main__":
foriinrange(3):
time.sleep(0.01)
print(f"{i}")
mem_leak()
print("mem_leak执行完成了.")
time.sleep(5)
运行效果。
python3main.py
0
1
2
mem_leak执行完成了.
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间。
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间。
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间
由于循环引用的存在,使得mem_leak函数就行执行完了其内部的局部变量引用计数器也不为0,所以内存得不到及时的释放。释放这部分内存有两个途径1、被Python内部的循环检测机制发现了;2、进程退出前的集中释放。
tracemalloc可以在一定程序上帮我们发现问题,在此就不讲怎么用了,我们直接上解决方案。Python为程序员提供了弱引用,通过这种方式可以不增加对象引用计数器的数值,这成为了我们打破循环引用的一种手段。
In[1]:importsys
In[2]:importweakref
In[3]:frommainimportPerson
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/evnpython3
importsys
importtime
importweakref
importthreading
classPerson(object):
free_lock=threading.Condition()
def__init__(self,name:str=""):
"""
Parameters
----------
name:str
姓名
best_friend:str
最要好的朋友名
"""
self._name=name
self._best_friend=None
@property
defbest_friend(self,person:"Person"):
returnself._best_friend
@best_friend.setter
defbest_friend(self,friend:"Person"):
self._best_friend=weakref.ref(friend)
def__str__(self):
"""
"""
returnself._name
def__del__(self):
"""
"""
self.free_lock.acquire()
print(f"{self._name}要GG了,现在释放它的内存空间。")
sys.stderr.flush()
self.free_lock.release()
defmem_leak():
"""
循环引用导致内存泄漏
"""
zhang_san=Person(name='张三')
li_si=Person("李四")
#构造出循环引用
#李四的好友是张三
li_si.best_friend=zhang_san
#张三的好友是李四
zhang_san.best_friend=li_si
if__name__=="__main__":
foriinrange(3):
time.sleep(0.01)
print(f"{i}")
mem_leak()
print("mem_leak执行完成了.")
time.sleep(5)
运行效果。
python3main.py
0
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间。
1
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间。
2
张三要GG了,现在释放它的内存空间。
李四要GG了,现在释放它的内存空间。
mem_leak执行完成了.
可以看到现在一旦函数执行完成,其内部的局部变量的内存就会得到释放,非常的及时。
外面库导致内存泄漏
这种情况我也只遇到过一次,之前mysql-connector-python的内存泄漏,导致我的程序跑着跑着占用的内存就越来越大;最后我们返的C语言扩展禁用之后就没有问题了。
以上就是总结python三种常见的内存泄漏场景的详细内容,更多关于python内存泄漏的资料请关注毛票票其它相关文章!