總結(jié)python 三種常見的內(nèi)存泄漏場(chǎng)景
概要
不要以為 Python 有自動(dòng)垃圾回收就不會(huì)內(nèi)存泄漏,本著它有“垃圾回收”我有“垃圾代碼”的精神,現(xiàn)在總結(jié)一下三種常見的內(nèi)存泄漏場(chǎng)景。
無(wú)窮大導(dǎo)致內(nèi)存泄漏
如果把內(nèi)存泄漏定義成只申請(qǐng)不釋放,那么借著 Python 中整數(shù)可以無(wú)窮大的這個(gè)特點(diǎn),我們一行代碼就可以完成內(nèi)存泄漏了。
i = 1024 ** 1024 ** 1024
循環(huán)引用導(dǎo)致內(nèi)存泄漏
引用記數(shù)器 是 Python 垃圾回收機(jī)制的基礎(chǔ),如果一個(gè)對(duì)象的引用數(shù)量不為 0 那么是不會(huì)被垃圾回收的,我們可以通過(guò) sys.getrefcount 來(lái)得到給定對(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
先來(lái)看一個(gè)循環(huán)引用的場(chǎng)景。
#!/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)檢測(cè)機(jī)制發(fā)現(xiàn)了; 2、進(jìn)程退出前的集中釋放。
tracemalloc 可以在一定程序上幫我們發(fā)現(xiàn)問(wèn)題,在此就不講怎么用了,我們直接上解決方案。Python 為程序員提供了弱引用,通過(guò)這種方式可以不增加對(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ù)來(lái)改造我們的代碼。
#!/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í)。
外面庫(kù)導(dǎo)致內(nèi)存泄漏
這種情況我也只遇到過(guò)一次,之前 mysql-connector-python 的內(nèi)存泄漏,導(dǎo)致我的程序跑著跑著占用的內(nèi)存就越來(lái)越大;最后我們返的 C 語(yǔ)言擴(kuò)展禁用之后就沒有問(wèn)題了。
以上就是總結(jié)python 三種常見的內(nèi)存泄漏場(chǎng)景的詳細(xì)內(nèi)容,更多關(guān)于python 內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在pycharm中運(yùn)行js文件以及附加node.js下載步驟
js文件需要用node來(lái)運(yùn)行,所以首先要安裝node軟件,下面這篇文章主要給大家介紹了關(guān)于在pycharm中運(yùn)行js文件以及附加node.js下載步驟的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12Python元組 tuple的概念與基本操作詳解【定義、創(chuàng)建、訪問(wèn)、計(jì)數(shù)、推導(dǎo)式等】
這篇文章主要介紹了Python元組 tuple的概念與基本操作,結(jié)合實(shí)例形式詳細(xì)分析了Python元組的定義、創(chuàng)建、訪問(wèn)、計(jì)數(shù)、推導(dǎo)式等常見操作技巧與操作注意事項(xiàng),需要的朋友可以參考下2019-10-10Python企業(yè)編碼生成系統(tǒng)之主程序模塊設(shè)計(jì)詳解
這篇文章主要介紹了Python企業(yè)編碼生成系統(tǒng)之主程序模塊設(shè)計(jì),包括初始化、界面與邏輯實(shí)現(xiàn)相關(guān)操作技巧,需要的朋友可以參考下2019-07-07如何利用Python實(shí)現(xiàn)一個(gè)論文降重工具
文章去重(或叫網(wǎng)頁(yè)去重)是根據(jù)文章(或網(wǎng)頁(yè))的文字內(nèi)容來(lái)判斷多個(gè)文章之間是否重復(fù),下面這篇文章主要給大家介紹了關(guān)于利用Python實(shí)現(xiàn)論文降重工具的相關(guān)資料,需要的朋友可以參考下2021-07-07Python使用微信itchat接口實(shí)現(xiàn)查看自己微信的信息功能詳解
這篇文章主要介紹了Python使用微信itchat接口實(shí)現(xiàn)查看自己微信的信息功能,結(jié)合實(shí)例形式分析了Python微信itchat模塊常見功能與操作技巧,需要的朋友可以參考下2019-08-08一文搞懂Python的hasattr()、getattr()、setattr()?函數(shù)用法
python中的getattr()、setattr()、hasattr()函數(shù)均是對(duì)類屬性或方法的操作,其中g(shù)etattr()用于獲取類或?qū)嵗兄付ǚ椒ǐ@取屬性的值,setattr()用于設(shè)置類或?qū)嵗袑傩曰蚍椒?hasattr()用于判斷類或?qū)嵗惺欠翊嬖谥付ǖ膶傩曰蚍椒?本文通過(guò)例子給大家詳解,一起看看吧2022-04-04Win10下python3.5和python2.7環(huán)境變量配置教程
這篇文章主要為大家詳細(xì)介紹了Win10下python3.5和python2.7環(huán)境變量配置教程,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09在pycharm中輸入import torch報(bào)錯(cuò)如何解決
這篇文章主要介紹了在pycharm中輸入import torch報(bào)錯(cuò)如何解決問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01