Python的垃圾回收機(jī)制深入分析
一、概述:
Python的GC模塊主要運(yùn)用了“引用計(jì)數(shù)”(reference counting)來(lái)跟蹤和回收垃圾。在引用計(jì)數(shù)的基礎(chǔ)上,還可以通過(guò)“標(biāo)記-清除”(mark and sweep)解決容器對(duì)象可能產(chǎn)生的循環(huán)引用的問(wèn)題。通過(guò)“分代回收”(generation collection)以空間換取時(shí)間來(lái)進(jìn)一步提高垃圾回收的效率。
二、引用計(jì)數(shù)
在Python中,大多數(shù)對(duì)象的生命周期都是通過(guò)對(duì)象的引用計(jì)數(shù)來(lái)管理的。從廣義上來(lái)講,引用計(jì)數(shù)也是一種垃圾收集機(jī)制,而且也是一種最直觀,最簡(jiǎn)單的垃圾收集技術(shù)。
原理:當(dāng)一個(gè)對(duì)象的引用被創(chuàng)建或者復(fù)制時(shí),對(duì)象的引用計(jì)數(shù)加1;當(dāng)一個(gè)對(duì)象的引用被銷毀時(shí),對(duì)象的引用計(jì)數(shù)減1;當(dāng)對(duì)象的引用計(jì)數(shù)減少為0時(shí),就意味著對(duì)象已經(jīng)沒(méi)有被任何人使用了,可以將其所占用的內(nèi)存釋放了。
雖然引用計(jì)數(shù)必須在每次分配和釋放內(nèi)存的時(shí)候加入管理引用計(jì)數(shù)的動(dòng)作,然而與其他主流的垃圾收集技術(shù)相比,引用計(jì)數(shù)有一個(gè)最大的有點(diǎn),即“實(shí)時(shí)性”,任何內(nèi)存,一旦沒(méi)有指向它的引用,就會(huì)立即被回收。而其他的垃圾收集計(jì)數(shù)必須在某種特殊條件下(比如內(nèi)存分配失?。┎拍苓M(jìn)行無(wú)效內(nèi)存的回收。
引用計(jì)數(shù)機(jī)制執(zhí)行效率問(wèn)題:引用計(jì)數(shù)機(jī)制所帶來(lái)的維護(hù)引用計(jì)數(shù)的額外操作與Python運(yùn)行中所進(jìn)行的內(nèi)存分配和釋放,引用賦值的次數(shù)是成正比的。而這點(diǎn)相比其他主流的垃圾回收機(jī)制,比如“標(biāo)記-清除”,“停止-復(fù)制”,是一個(gè)弱點(diǎn),因?yàn)檫@些技術(shù)所帶來(lái)的額外操作基本上只是與待回收的內(nèi)存數(shù)量有關(guān)。
如果說(shuō)執(zhí)行效率還僅僅是引用計(jì)數(shù)機(jī)制的一個(gè)軟肋的話,那么很不幸,引用計(jì)數(shù)機(jī)制還存在著一個(gè)致命的弱點(diǎn),正是由于這個(gè)弱點(diǎn),使得俠義的垃圾收集從來(lái)沒(méi)有將引用計(jì)數(shù)包含在內(nèi),能引發(fā)出這個(gè)致命的弱點(diǎn)就是循環(huán)引用(也稱交叉引用)。
問(wèn)題說(shuō)明:
循環(huán)引用可以使一組對(duì)象的引用計(jì)數(shù)不為0,然而這些對(duì)象實(shí)際上并沒(méi)有被任何外部對(duì)象所引用,它們之間只是相互引用。這意味著不會(huì)再有人使用這組對(duì)象,應(yīng)該回收這組對(duì)象所占用的內(nèi)存空間,然后由于相互引用的存在,每一個(gè)對(duì)象的引用計(jì)數(shù)都不為0,因此這些對(duì)象所占用的內(nèi)存永遠(yuǎn)不會(huì)被釋放。比如:
a = [] b = [] a.append(b) b.append(a) print a [[[…]]] print b [[[…]]]
這一點(diǎn)是致命的,這與手動(dòng)進(jìn)行內(nèi)存管理所產(chǎn)生的內(nèi)存泄露毫無(wú)區(qū)別。
要解決這個(gè)問(wèn)題,Python引入了其他的垃圾收集機(jī)制來(lái)彌補(bǔ)引用計(jì)數(shù)的缺陷:“標(biāo)記-清除”,“分代回收”兩種收集技術(shù)。
三、標(biāo)記-清除
“標(biāo)記-清除”是為了解決循環(huán)引用的問(wèn)題??梢园渌麑?duì)象引用的容器對(duì)象(比如:list,set,dict,class,instance)都可能產(chǎn)生循環(huán)引用。
我們必須承認(rèn)一個(gè)事實(shí),如果兩個(gè)對(duì)象的引用計(jì)數(shù)都為1,但是僅僅存在他們之間的循環(huán)引用,那么這兩個(gè)對(duì)象都是需要被回收的,也就是說(shuō),它們的引用計(jì)數(shù)雖然表現(xiàn)為非0,但實(shí)際上有效的引用計(jì)數(shù)為0。我們必須先將循環(huán)引用摘掉,那么這兩個(gè)對(duì)象的有效計(jì)數(shù)就現(xiàn)身了。假設(shè)兩個(gè)對(duì)象為A、B,我們從A出發(fā),因?yàn)樗幸粋€(gè)對(duì)B的引用,則將B的引用計(jì)數(shù)減1;然后順著引用達(dá)到B,因?yàn)锽有一個(gè)對(duì)A的引用,同樣將A的引用減1,這樣,就完成了循環(huán)引用對(duì)象間環(huán)摘除。
但是這樣就有一個(gè)問(wèn)題,假設(shè)對(duì)象A有一個(gè)對(duì)象引用C,而C沒(méi)有引用A,如果將C計(jì)數(shù)引用減1,而最后A并沒(méi)有被回收,顯然,我們錯(cuò)誤的將C的引用計(jì)數(shù)減1,這將導(dǎo)致在未來(lái)的某個(gè)時(shí)刻出現(xiàn)一個(gè)對(duì)C的懸空引用。這就要求我們必須在A沒(méi)有被刪除的情況下復(fù)原C的引用計(jì)數(shù),如果采用這樣的方案,那么維護(hù)引用計(jì)數(shù)的復(fù)雜度將成倍增加。
原理:“標(biāo)記-清除”采用了更好的做法,我們并不改動(dòng)真實(shí)的引用計(jì)數(shù),而是將集合中對(duì)象的引用計(jì)數(shù)復(fù)制一份副本,改動(dòng)該對(duì)象引用的副本。對(duì)于副本做任何的改動(dòng),都不會(huì)影響到對(duì)象生命走起的維護(hù)。
這個(gè)計(jì)數(shù)副本的唯一作用是尋找root object集合(該集合中的對(duì)象是不能被回收的)。當(dāng)成功尋找到root object集合之后,首先將現(xiàn)在的內(nèi)存鏈表一分為二,一條鏈表中維護(hù)root object集合,成為root鏈表,而另外一條鏈表中維護(hù)剩下的對(duì)象,成為unreachable鏈表。之所以要剖成兩個(gè)鏈表,是基于這樣的一種考慮:現(xiàn)在的unreachable可能存在被root鏈表中的對(duì)象,直接或間接引用的對(duì)象,這些對(duì)象是不能被回收的,一旦在標(biāo)記的過(guò)程中,發(fā)現(xiàn)這樣的對(duì)象,就將其從unreachable鏈表中移到root鏈表中;當(dāng)完成標(biāo)記后,unreachable鏈表中剩下的所有對(duì)象就是名副其實(shí)的垃圾對(duì)象了,接下來(lái)的垃圾回收只需限制在unreachable鏈表中即可。
四、分代回收
背景:分代的垃圾收集技術(shù)是在上個(gè)世紀(jì)80年代初發(fā)展起來(lái)的一種垃圾收集機(jī)制,一系列的研究表明:無(wú)論使用何種語(yǔ)言開(kāi)發(fā),無(wú)論開(kāi)發(fā)的是何種類型,何種規(guī)模的程序,都存在這樣一點(diǎn)相同之處。即:一定比例的內(nèi)存塊的生存周期都比較短,通常是幾百萬(wàn)條機(jī)器指令的時(shí)間,而剩下的內(nèi)存塊,起生存周期比較長(zhǎng),甚至?xí)某绦蜷_(kāi)始一直持續(xù)到程序結(jié)束。
從前面“標(biāo)記-清除”這樣的垃圾收集機(jī)制來(lái)看,這種垃圾收集機(jī)制所帶來(lái)的額外操作實(shí)際上與系統(tǒng)中總的內(nèi)存塊的數(shù)量是相關(guān)的,當(dāng)需要回收的內(nèi)存塊越多時(shí),垃圾檢測(cè)帶來(lái)的額外操作就越多,而垃圾回收帶來(lái)的額外操作就越少;反之,當(dāng)需回收的內(nèi)存塊越少時(shí),垃圾檢測(cè)就將比垃圾回收帶來(lái)更少的額外操作。為了提高垃圾收集的效率,采用“空間換時(shí)間的策略”。
原理:將系統(tǒng)中的所有內(nèi)存塊根據(jù)其存活時(shí)間劃分為不同的集合,每一個(gè)集合就成為一個(gè)“代”,垃圾收集的頻率隨著“代”的存活時(shí)間的增大而減小。也就是說(shuō),活得越長(zhǎng)的對(duì)象,就越不可能是垃圾,就應(yīng)該減少對(duì)它的垃圾收集頻率。那么如何來(lái)衡量這個(gè)存活時(shí)間:通常是利用幾次垃圾收集動(dòng)作來(lái)衡量,如果一個(gè)對(duì)象經(jīng)過(guò)的垃圾收集次數(shù)越多,可以得出:該對(duì)象存活時(shí)間就越長(zhǎng)。
舉例說(shuō)明:
當(dāng)某些內(nèi)存塊M經(jīng)過(guò)了3次垃圾收集的清洗之后還存活時(shí),我們就將內(nèi)存塊M劃到一個(gè)集合A中去,而新分配的內(nèi)存都劃分到集合B中去。當(dāng)垃圾收集開(kāi)始工作時(shí),大多數(shù)情況都只對(duì)集合B進(jìn)行垃圾回收,而對(duì)集合A進(jìn)行垃圾回收要隔相當(dāng)長(zhǎng)一段時(shí)間后才進(jìn)行,這就使得垃圾收集機(jī)制需要處理的內(nèi)存少了,效率自然就提高了。在這個(gè)過(guò)程中,集合B中的某些內(nèi)存塊由于存活時(shí)間長(zhǎng)而會(huì)被轉(zhuǎn)移到集合A中,當(dāng)然,集合A中實(shí)際上也存在一些垃圾,這些垃圾的回收會(huì)因?yàn)檫@種分代的機(jī)制而被延遲。
在Python中,總共有3“代”,也就是Python實(shí)際上維護(hù)了3條鏈表。具體可以查看Python源碼詳細(xì)了解。
相關(guān)文章
Django使用Profile擴(kuò)展User模塊方式
這篇文章主要介紹了Django使用Profile擴(kuò)展User模塊方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05python機(jī)器學(xué)習(xí)創(chuàng)建基于規(guī)則聊天機(jī)器人過(guò)程示例詳解
這篇文章主要為大家介紹了python實(shí)現(xiàn)基于規(guī)則聊天機(jī)器人的過(guò)程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-11-11matlab、python中矩陣的互相導(dǎo)入導(dǎo)出方式
這篇文章主要介紹了matlab、python中矩陣的互相導(dǎo)入導(dǎo)出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06詳解如何利用Pytest?Cache?Fixture實(shí)現(xiàn)測(cè)試結(jié)果緩存
這篇文章主要為大家詳細(xì)介紹了如何利用Pytest?Cache?Fixture實(shí)現(xiàn)測(cè)試結(jié)果緩存,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-09-09Django自定義分頁(yè)與bootstrap分頁(yè)結(jié)合
這篇文章主要為大家詳細(xì)介紹了Django自定義分頁(yè)與bootstrap分頁(yè)結(jié)合使用的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05關(guān)于python基礎(chǔ)數(shù)據(jù)類型bytes進(jìn)制轉(zhuǎn)換
Python 3.x之后,Python自帶字符默認(rèn)使用utf-8格式編碼和顯示,bytes數(shù)據(jù)類型是utf-8格式的二進(jìn)制形式的不可變序列,需要的朋友可以參考下2023-05-05Python基于callable函數(shù)檢測(cè)對(duì)象是否可被調(diào)用
這篇文章主要介紹了Python基于callable函數(shù)檢測(cè)對(duì)象是否可被調(diào)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10Python數(shù)據(jù)結(jié)構(gòu)與算法之字典樹(shù)實(shí)現(xiàn)方法示例
這篇文章主要介紹了Python數(shù)據(jù)結(jié)構(gòu)與算法之字典樹(shù)實(shí)現(xiàn)方法,可實(shí)現(xiàn)針對(duì)單詞出現(xiàn)次數(shù)的統(tǒng)計(jì)功能,涉及Python樹(shù)結(jié)構(gòu)的定義、遍歷及統(tǒng)計(jì)等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12Django框架模板語(yǔ)言實(shí)例小結(jié)【變量,標(biāo)簽,過(guò)濾器,繼承,html轉(zhuǎn)義】
這篇文章主要介紹了Django框架模板語(yǔ)言,結(jié)合實(shí)例形式總結(jié)分析了Django框架中變量,標(biāo)簽,過(guò)濾器,繼承,html轉(zhuǎn)義等相關(guān)模板語(yǔ)言操作技巧,需要的朋友可以參考下2019-05-05Python通過(guò)paramiko庫(kù)實(shí)現(xiàn)遠(yuǎn)程執(zhí)行l(wèi)inux命令的方法
這篇文章主要介紹了Python通過(guò)paramiko庫(kù)實(shí)現(xiàn)遠(yuǎn)程執(zhí)行l(wèi)inux命令,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03