這兩天和朋友討論問題,期間提到了垃圾回收機(jī)制,我立馬想到了 python 的垃圾回收機(jī)制翩剪,之前有看過相關(guān)的資料,但是還是覺得理解不深刻彩郊,所以又去找了相關(guān)資料肢专。
垃圾回收機(jī)制
python 和 java 一樣,采用了垃圾回收機(jī)制焦辅,而像 c/c++ 這種語言是沒有采用垃圾回收機(jī)制的,因此當(dāng)一個(gè)對(duì)象再也不用時(shí)椿胯,程序員必須自己釋放內(nèi)存筷登。但采用了垃圾回收機(jī)制的語言--如 python,程序員只管創(chuàng)建和使用哩盲,不管釋放和回收前方。
python 的垃圾回收機(jī)制
當(dāng)前的垃圾回收算法多種多樣,python 采用的是引用計(jì)數(shù)為主廉油,標(biāo)記清除和分代回收為輔的垃圾回收機(jī)制惠险。
這是由多種原因決定的,有興趣的自行搜索一些資料抒线。
引用計(jì)數(shù)
原理:當(dāng)創(chuàng)建或賦值對(duì)象的引用時(shí)班巩,對(duì)象的引用計(jì)數(shù)加1;當(dāng)銷毀對(duì)象的引用時(shí)嘶炭,對(duì)象的引用計(jì)數(shù)減1抱慌;當(dāng)對(duì)象的引用計(jì)數(shù)值為0時(shí),則說明對(duì)象已經(jīng)沒有被引用了眨猎,可以將對(duì)象所占用的內(nèi)存釋放抑进。
導(dǎo)致引用計(jì)數(shù) +1 的情況:
- 對(duì)象被創(chuàng)建;
- 對(duì)象被引用睡陪;
- 對(duì)象作為參數(shù)傳入函數(shù)中寺渗;
- 對(duì)象作為元素存儲(chǔ)在容器中。
導(dǎo)致引用計(jì)數(shù) -1 的情況:
- 對(duì)象被銷毀兰迫;
- 對(duì)象的引用指向了新的對(duì)象信殊;
- 對(duì)象離開了它的作用域;
- 對(duì)象所在的容器被銷毀汁果。
引用計(jì)數(shù)的優(yōu)點(diǎn):
- 無需掛起程序鸡号;(相對(duì)于標(biāo)記清除法)
- 引用局部性比較好;
- 廢棄及回收须鼎。
引用計(jì)數(shù)的缺點(diǎn):
- 更新引用計(jì)數(shù)值的花銷鲸伴;
- 引用計(jì)數(shù)占據(jù)額外的空間府蔗;
- 無法處理環(huán)形引用的情況。
環(huán)形引用的產(chǎn)生
什么是環(huán)形引用汞窗?比如姓赤, A 對(duì)象里引用了 B 對(duì)象,B 對(duì)象引用了 A 對(duì)象仲吏,這樣就形成了環(huán)形引用不铆。
class A:
pass
a = A() # 這里 a 的引用是 1 次
b = A() # 這里同上
a.t = b # 這里 b 的引用 +1 ,因?yàn)?b 的引用為 2 次
b.t = a # 這里同上
del b # 這里 b 的引用 -1裹唆,因?yàn)樵瓉?b 的引用是 2誓斥,-1 之后是1,a 的引用仍然為 2
del a # 這里同上
# 現(xiàn)在 a许帐,b 已經(jīng)被刪了劳坑,但之前它們指向的對(duì)象的引用計(jì)數(shù)值仍為 1,不為 0成畦,
# 因此引用計(jì)數(shù)算法仍然認(rèn)為這兩個(gè)對(duì)象不是垃圾對(duì)象距芬,這就是循環(huán)引用帶來的問題。
python 為了解決循環(huán)引用的問題循帐,引入了標(biāo)記清除和分代回收框仔。
標(biāo)記清除
標(biāo)記清除也是著名的垃圾回收算法之一,最典型的就是 java 采用了這個(gè)算法拄养。
原理:
- 標(biāo)記階段:對(duì)所有存活對(duì)象進(jìn)行一次全局遍歷來進(jìn)行對(duì)象的標(biāo)記离斩,所有可達(dá)對(duì)象標(biāo)記為可達(dá),其它不可達(dá)的對(duì)象就是可以被回收的垃圾對(duì)象瘪匿。
- 清除階段:清除所有垃圾對(duì)象捐腿。
標(biāo)記清除的優(yōu)點(diǎn):
- 沒有環(huán)形引用的問題(相對(duì)與引用計(jì)數(shù));
- 無需操作引用計(jì)數(shù)值的開銷(相對(duì)與引用計(jì)數(shù))柿顶。
標(biāo)記清除的缺點(diǎn):
- 垃圾回收進(jìn)行時(shí)茄袖,程序必須暫停。
- 標(biāo)記階段的花銷較大
- 清除對(duì)象后會(huì)造成內(nèi)存碎片的問題(解決方法是采用標(biāo)記縮進(jìn)算法嘁锯,這里不再詳述)
在 python 中宪祥,標(biāo)記清除主要是為了解決循環(huán)引用的問題。
python 會(huì)用鏈表連接可能產(chǎn)生循環(huán)引用的對(duì)象(如 list家乘,dict蝗羊,class 等容器類,int仁锯,string這類不會(huì)產(chǎn)生循環(huán)引用)耀找,如局骤,a=[],b=[],c={},將會(huì)產(chǎn)生:head <----> a <----> b <----> c 雙向鏈表饭寺。然后從這些鏈表里的元素出發(fā)氮昧,標(biāo)記每個(gè)可到達(dá)的對(duì)象娶桦,然后那些沒有被標(biāo)記的對(duì)象將會(huì)被清除。
流程:
- 尋找根集合狞悲,如上面的鏈表撮抓,里面的元素一般為全局引用或函數(shù)棧上的引用
- 從根出發(fā),可到達(dá)對(duì)象會(huì)被標(biāo)記
- 清除所有沒有被標(biāo)記的對(duì)象
分代回收
分代回收在我看來摇锋,是為了提高垃圾回收效率和程序性能的的機(jī)制丹拯。它作用的地方并不是垃圾回收的內(nèi)容,而是垃圾回收這個(gè)動(dòng)作荸恕。
分代收集的思想就是活的越久的對(duì)象乖酬,就越不是垃圾,回收的頻率就應(yīng)該越低
--《Python垃圾回收機(jī)制及gc模塊詳解》
這個(gè)分代回收非常重要的原因是:一部分對(duì)象的生存周期比較短融求,一部分對(duì)象的生存周期很長(zhǎng)咬像,甚至?xí)掷m(xù)到程序結(jié)束。
這樣的話双肤,采用標(biāo)記清除時(shí),如果都一視同仁的話钮惠,會(huì)有效率的問題茅糜。
比如說,在某個(gè)對(duì)象的集合中素挽,標(biāo)記清除對(duì)象是 1s 進(jìn)行一次蔑赘,在進(jìn)行了 10 次(共10s)的標(biāo)記清除后,它發(fā)現(xiàn)一部分對(duì)象存在了 10 次预明,因此它把這部分對(duì)象移入另一個(gè)對(duì)象的集合中缩赛,對(duì)這些對(duì)象進(jìn)行 10s 一次的標(biāo)記清除,這樣的話會(huì)比之前不區(qū)分的時(shí)候效率高撰糠,占用的資源少酥馍。
python 的分代回收分三個(gè)代。(三個(gè)代其實(shí)是三個(gè)鏈表)
當(dāng)各個(gè)代中的對(duì)象數(shù)量達(dá)到閾值的時(shí)候就會(huì)觸發(fā) python 的垃圾回收阅酪。(具體可用 gc 模塊的 get_threshold() 查看)
python 首先從第三代開始檢查旨袒,如果三代中的對(duì)象大于閾值則同時(shí)回收三個(gè)代的對(duì)象,如果二代的的對(duì)象大于閾值术辐, 則回收二代和一代的對(duì)象砚尽。
如有錯(cuò)誤,麻煩評(píng)論告知辉词,無比感激必孤!
參考
http://www.jb51.net/article/52229.htm
http://www.cnblogs.com/Xjng/p/5128269.html
https://my.oschina.net/hnuweiwei/blog/291367?p=1
http://blog.csdn.net/yueguanghaidao/article/details/11274737