總結(jié)python 三種常見的內(nèi)存泄漏場景
概要
不要以為 Python 有自動(dòng)垃圾回收就不會(huì)內(nèi)存泄漏,本著它有“垃圾回收”我有“垃圾代碼”的精神,現(xiàn)在總結(jié)一下三種常見的內(nèi)存泄漏場景。
無窮大導(dǎo)致內(nèi)存泄漏
如果把內(nèi)存泄漏定義成只申請(qǐng)不釋放,那么借著 Python 中整數(shù)可以無窮大的這個(gè)特點(diǎn),我們一行代碼就可以完成內(nèi)存泄漏了。
i = 1024 ** 1024 ** 1024
循環(huán)引用導(dǎo)致內(nèi)存泄漏
引用記數(shù)器 是 Python 垃圾回收機(jī)制的基礎(chǔ),如果一個(gè)對(duì)象的引用數(shù)量不為 0 那么是不會(huì)被垃圾回收的,我們可以通過 sys.getrefcount 來得到給定對(duì)象的引用數(shù)量。
In [1]: import sys
In [2]: a = {'name':'tom','age':16}
In [3]: sys.getrefcount(a) # 由于 getrefcount 內(nèi)部也會(huì)臨時(shí)的引用 a 所以,使得計(jì)數(shù)器的值變成了 2 。
Out[3]: 2
In [4]: b = a
In [5]: sys.getrefcount(a)
Out[5]: 3
先來看一個(gè)循環(huán)引用的場景。
#!/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 了,現(xiàn)在釋放它的內(nèi)存空間。")
sys.stderr.flush()
self.free_lock.release()
def mem_leak():
"""
循環(huán)引用導(dǎo)致內(nèi)存泄漏
"""
zhang_san = Person(name='張三')
li_si = Person("李四")
# 構(gòu)造出循環(huán)引用
# 李四的好友是張三
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 執(zhí)行完成了.")
time.sleep(5)
運(yùn)行效果。
python3 main.py
0
1
2
mem_leak 執(zhí)行完成了.
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間
由于循環(huán)引用的存在,使得 mem_leak 函數(shù)就行執(zhí)行完了其內(nèi)部的局部變量引用計(jì)數(shù)器也不為 0 ,所以內(nèi)存得不到及時(shí)的釋放。釋放這部分內(nèi)存有兩個(gè)途徑 1、 被 Python 內(nèi)部的循環(huán)檢測機(jī)制發(fā)現(xiàn)了; 2、進(jìn)程退出前的集中釋放。
tracemalloc 可以在一定程序上幫我們發(fā)現(xiàn)問題,在此就不講怎么用了,我們直接上解決方案。Python 為程序員提供了弱引用,通過這種方式可以不增加對(duì)象引用計(jì)數(shù)器的數(shù)值,這成為了我們打破循環(huán)引用的一種手段。
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) # 弱引用不會(huì)增加計(jì)數(shù)器的值
Out[7]: 2
現(xiàn)在使用 weakref 技術(shù)來改造我們的代碼。
#!/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 了,現(xiàn)在釋放它的內(nèi)存空間。")
sys.stderr.flush()
self.free_lock.release()
def mem_leak():
"""
循環(huán)引用導(dǎo)致內(nèi)存泄漏
"""
zhang_san = Person(name='張三')
li_si = Person("李四")
# 構(gòu)造出循環(huán)引用
# 李四的好友是張三
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 執(zhí)行完成了.")
time.sleep(5)
運(yùn)行效果。
python3 main.py
0
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
1
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
2
張三 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
李四 要 GG 了,現(xiàn)在釋放它的內(nèi)存空間。
mem_leak 執(zhí)行完成了.
可以看到現(xiàn)在一旦函數(shù)執(zhí)行完成,其內(nèi)部的局部變量的內(nèi)存就會(huì)得到釋放,非常的及時(shí)。
外面庫導(dǎo)致內(nèi)存泄漏
這種情況我也只遇到過一次,之前 mysql-connector-python 的內(nèi)存泄漏,導(dǎo)致我的程序跑著跑著占用的內(nèi)存就越來越大;最后我們返的 C 語言擴(kuò)展禁用之后就沒有問題了。
以上就是總結(jié)python 三種常見的內(nèi)存泄漏場景的詳細(xì)內(nèi)容,更多關(guān)于python 內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python進(jìn)程類subprocess的一些操作方法例子
這篇文章主要介紹了python進(jìn)程類subprocess的一些操作方法例子,本文講解了Popen、wait、poll、kill、communicate等方法的實(shí)際操作例子,需要的朋友可以參考下2014-11-11
Python對(duì)象的底層實(shí)現(xiàn)源碼學(xué)習(xí)
這篇文章主要為大家介紹了Python對(duì)象的底層實(shí)現(xiàn)源碼學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
python讀取浮點(diǎn)數(shù)和讀取文本文件示例
這篇文章主要介紹了python讀取浮點(diǎn)數(shù)和讀取文本文件示例,需要的朋友可以參考下2014-05-05
基于Keras的格式化輸出Loss實(shí)現(xiàn)方式
這篇文章主要介紹了基于Keras的格式化輸出Loss實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-06-06
pandas中的DataFrame數(shù)據(jù)遍歷解讀
這篇文章主要介紹了pandas中的DataFrame數(shù)據(jù)遍歷解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
安裝pytorch時(shí)報(bào)sslerror錯(cuò)誤的解決方案
這篇文章主要介紹了安裝pytorch時(shí)報(bào)sslerror錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05
python基于mysql實(shí)現(xiàn)的簡單隊(duì)列以及跨進(jìn)程鎖實(shí)例詳解
這篇文章主要介紹了python基于mysql實(shí)現(xiàn)的簡單隊(duì)列以及跨進(jìn)程鎖,需要的朋友可以參考下2014-07-07
Python干貨實(shí)戰(zhàn)之逆向登錄世界上最大的游戲平臺(tái)Stream
有些網(wǎng)頁中的數(shù)據(jù)進(jìn)行了算法加密 這些算法代碼是JavaScript 加密的地方就是在js文件里,我們需要破解這些算法加密 就需要了解這加密的過程 獲取加密過程中的代碼 然后進(jìn)行后續(xù)的反反爬蟲操作2021-10-10
詳解win10下pytorch-gpu安裝以及CUDA詳細(xì)安裝過程
這篇文章主要介紹了win10下pytorch-gpu安裝以及CUDA詳細(xì)安裝過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

