Python进阶:程序界的垃圾分类回收
垃圾回收是 Python 自帶的機(jī)制,用于自動(dòng)釋放不會(huì)再用到的內(nèi)存空間;
什么是內(nèi)存泄漏呢?
- 內(nèi)存泄漏,并不是說(shuō)你的內(nèi)存出現(xiàn)了信息安全問(wèn)題,被惡意程序利用了,而是指程序本身沒(méi)有設(shè)計(jì)好,導(dǎo)致程序未能釋放已不再使用的內(nèi)存。
- 內(nèi)存泄漏也不是指你的內(nèi)存在物理上消失了,而是意味著代碼在分配了某段內(nèi)存后,因?yàn)樵O(shè)計(jì)錯(cuò)誤,失去了對(duì)這段內(nèi)存的控制,從而造成了內(nèi)存的浪費(fèi)。
計(jì)數(shù)引用
Python 中一切皆對(duì)象。當(dāng)這個(gè)對(duì)象的引用計(jì)數(shù)(指針數(shù))為 0 的時(shí)候,說(shuō)明這個(gè)對(duì)象永不可達(dá),自然它也就成為了垃圾,需要被回收。
例:
# 顯示當(dāng)前 python 程序占用的內(nèi)存大小 def show_memory_info(hint):pid = os.getpid()p = psutil.Process(pid)info = p.memory_full_info()memory = info.uss / 1024. / 1024print('{} memory used: {} MB'.format(hint, memory)) def func():show_memory_info('initial')a = [i for i in range(10000000)]show_memory_info('after a created')func() show_memory_info('finished')########## 輸出 ########## # initial memory used: 6.62890625 MB # after a created memory used: 199.33203125 MB # finished memory used: 7.6640625 MB程序初始化時(shí)占的內(nèi)存為6MB,接著創(chuàng)建了一個(gè)列表a,由于a還沒(méi)被回收,因此占的內(nèi)存升到了200MB,當(dāng)函數(shù)返回后,a的引用計(jì)數(shù)為0,a被回收,內(nèi)存又恢復(fù)到了7MB。
如果把a(bǔ)變成全局變量,函數(shù)返回后,引用計(jì)數(shù)依然大于0,于是對(duì)象就不會(huì)被垃圾回收,依然占著大量的內(nèi)存
def func():show_memory_info('initial')global aa = [i for i in range(10000000)]show_memory_info('after a created')func() show_memory_info('finished')########## 輸出 ########### initial memory used: 6.67578125 MB # after a created memory used: 199.30859375 MB # finished memory used: 199.30859375 MB或者把列表返回,在主程序中接收,引用依然存在,垃圾回收就不會(huì)被觸發(fā),大量?jī)?nèi)存仍然被占用著
def func():show_memory_info('initial')a = [i for i in range(10000000)]show_memory_info('after a created')return aa = func() show_memory_info('finished')########## 輸出 ########### initial memory used: 6.6484375 MB # after a created memory used: 199.2890625 MB # finished memory used: 199.2890625 MB看一下 Python 內(nèi)部的引用計(jì)數(shù)機(jī)制
import sysa = []# 兩次引用,一次來(lái)自 a,一次來(lái)自 getrefcount print(sys.getrefcount(a))def func(a):# 四次引用,a,python 的函數(shù)調(diào)用棧,函數(shù)參數(shù),和 getrefcountprint(sys.getrefcount(a))func(a)# 兩次引用,一次來(lái)自 a,一次來(lái)自 getrefcount,函數(shù) func 調(diào)用已經(jīng)不存在 print(sys.getrefcount(a))########## 輸出 ##########2 4 2sys.getrefcount() 這個(gè)函數(shù),可以查看一個(gè)變量的引用次數(shù)。這段代碼本身應(yīng)該很好理解,不過(guò)別忘了,getrefcount 本身也會(huì)引入一次計(jì)數(shù)。另一個(gè)要注意的是,在函數(shù)調(diào)用發(fā)生的時(shí)候,會(huì)產(chǎn)生額外的兩次引用,一次來(lái)自函數(shù)棧,另一個(gè)是函數(shù)參數(shù)。
又如:
import sysa = []print(sys.getrefcount(a)) # 兩次 b = aprint(sys.getrefcount(a)) # 三次 c = b d = b e = c f = e g = dprint(sys.getrefcount(a)) # 八次########## 輸出 ##########2 3 8a、b、c、d、e、f、g 這些變量全部指代的是同一個(gè)對(duì)象,而 sys.getrefcount() 函數(shù)并不是統(tǒng)計(jì)一個(gè)指針,而是要統(tǒng)計(jì)一個(gè)對(duì)象被引用的次數(shù),所以最后一共會(huì)有八次引用。
手動(dòng)釋放內(nèi)存,應(yīng)該怎么做呢? 方法同樣很簡(jiǎn)單。只需要先調(diào)用 del a 來(lái)刪除一個(gè)對(duì)象;然后強(qiáng)制調(diào)用 gc.collect(),即可手動(dòng)啟動(dòng)垃圾回收。
import gc import os import psutil # 顯示當(dāng)前 python 程序占用的內(nèi)存大小 def show_memory_info(hint):pid = os.getpid()p = psutil.Process(pid)info = p.memory_full_info()memory = info.uss / 1024. / 1024print('{} memory used: {} MB'.format(hint, memory))show_memory_info('initial')a = [i for i in range(10000000)]show_memory_info('after a created')del a gc.collect()show_memory_info('finish') print(a)initial memory used: 6.54296875 MB after a created memory used: 199.17578125 MB finish memory used: 7.26171875 MB Traceback (most recent call last):File "Coroutine.py", line 24, in <module>print(a) NameError: name 'a' is not defined循環(huán)引用
? 觀察代碼:
def func():show_memory_info('initial')a = [i for i in range(10000000)]b = [i for i in range(10000000)]show_memory_info('after a, b created')a.append(b)b.append(a)func() show_memory_info('finished')########## 輸出 ##########? initial memory used: 6.625 MB
? after a, b created memory used: 392.08984375 MB
? finished memory used: 392.08984375 MB
這里,a 和 b 互相引用,并且,作為局部變量,在函數(shù) func 調(diào)用結(jié)束后,a 和 b 這兩個(gè)指針從程序意義上已經(jīng)不存在了。但是,很明顯,依然有內(nèi)存占用!為什么呢?因?yàn)榛ハ嘁?#xff0c;導(dǎo)致它們的引用數(shù)都不為 0。
處理這種情況,可以調(diào)用顯式調(diào)用 gc.collect() ,來(lái)啟動(dòng)垃圾回收。
Python 使用標(biāo)記清除(mark-sweep)算法和分代收集(generational),來(lái)啟用針對(duì)循環(huán)引用的自動(dòng)垃圾回收。
調(diào)試內(nèi)存泄漏
objgraph,一個(gè)非常好用的可視化引用關(guān)系的包.
安裝:
pip install graphviz pip install xdot pip install objgraphwindows的話要除了裝以上庫(kù)還要在官網(wǎng)https://graphviz.gitlab.io/_pages/Download/Download_windows.html下載,然后設(shè)置環(huán)境變量 Path增加C:\Program Files (x86)\Graphviz2.38\bin,在CMD輸入dot -version驗(yàn)證。
通過(guò)下面這段代碼和生成的引用調(diào)用圖,你能非常直觀地發(fā)現(xiàn),有兩個(gè) list 互相引用,說(shuō)明這里極有可能引起內(nèi)存泄露。
import objgrapha = [1, 2, 3] b = [4, 5, 6]a.append(b) b.append(a)objgraph.show_refs([a])
注:在windows中可能會(huì)提示:
Graph written to C:\Users\Public\Documents\Wondershare\CreatorTemp\objgraph-wwcqiie_.dot (8 nodes) Image renderer (dot) not found, not doing anything else這時(shí)只要在打開dot文件所在的路徑,然后CMD中執(zhí)行
dot .\objgraph-yclwfpzr.dot -Tpng -o image.png就可以生成文件。
另一個(gè)非常有用的函數(shù),是 show_backrefs()。以下是調(diào)用show_backrefs()生成的圖片。
?參考
極客時(shí)間《Python核心技術(shù)與實(shí)戰(zhàn)》專欄
轉(zhuǎn)載于:https://www.cnblogs.com/xiaoguanqiu/p/11197604.html
總結(jié)
以上是生活随笔為你收集整理的Python进阶:程序界的垃圾分类回收的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Cisco 交換機命名規則
- 下一篇: JavaScript中的true和fal