Python的GC模塊主要運用了“引用計數(shù)”(reference counting)來跟蹤和回收垃圾堰氓。在引用計數(shù)的基礎(chǔ)上挤渐,還可以通過“標記-清除”(mark and sweep)解決容器對象可能產(chǎn)生的循環(huán)引用的問題。通過“分代回收”(generation collection)以空間換取時間來進一步提高垃圾回收的效率双絮。
一浴麻、引用計數(shù)
原理:當(dāng)一個對象的引用被創(chuàng)建或者復(fù)制時,對象的引用計數(shù)加1囤攀;當(dāng)一個對象的引用被銷毀時软免,對象的引用計數(shù)減1;當(dāng)對象的引用計數(shù)減少為0時焚挠,就意味著對象已經(jīng)沒有被任何人使用了膏萧,可以將其所占用的內(nèi)存釋放了。
優(yōu)點:實時性蝌衔,任何內(nèi)存向抢,一旦沒有指向它的引用,就會立即被回收胚委。而其他的垃圾收集計數(shù)必須在某種特殊條件下(比如內(nèi)存分配失斝)才能進行無效內(nèi)存的回收。
缺點:執(zhí)行效率不高亩冬,引用計數(shù)機制所帶來的維護引用計數(shù)的額外操作與Python運行中所進行的內(nèi)存分配和釋放艘希,引用賦值的次數(shù)是成正比的。而這點相比其他主流的垃圾回收機制硅急,比如“標記-清除”覆享,“停止-復(fù)制”,是一個弱點营袜,因為這些技術(shù)所帶來的額外操作基本上只是與待回收的內(nèi)存數(shù)量有關(guān)撒顿。
致命問題:循環(huán)引用(也稱交叉引用),循環(huán)引用可以使一組對象的引用計數(shù)不為0荚板,然而這些對象實際上并沒有被任何外部對象所引用凤壁,它們之間只是相互引用吩屹。這意味著不會再有人使用這組對象,應(yīng)該回收這組對象所占用的內(nèi)存空間拧抖,然后由于相互引用的存在煤搜,每一個對象的引用計數(shù)都不為0,因此這些對象所占用的內(nèi)存永遠不會被釋放唧席。
eg:
a = []
b = []
a.append(b)
b.append(b)
print a
[[[…]]]
print b
[[[…]]]
二擦盾、標記清除機制
標記-清除”是為了解決循環(huán)引用的問題√视矗可以包含其他對象引用的容器對象(比如:list迹卢,set,dict徒仓,class腐碱,instance)都可能產(chǎn)生循環(huán)引用。
如果兩個對象的引用計數(shù)都為1蓬衡,但是僅僅存在他們之間的循環(huán)引用,那么這兩個對象都是需要被回收的彤枢,也就是說狰晚,它們的引用計數(shù)雖然表現(xiàn)為非0,但實際上有效的引用計數(shù)為0缴啡。我們必須先將循環(huán)引用摘掉壁晒,那么這兩個對象的有效計數(shù)就現(xiàn)身了。假設(shè)兩個對象為A业栅、B秒咐,我們從A出發(fā),因為它有一個對B的引用碘裕,則將B的引用計數(shù)減1携取;然后順著引用達到B,因為B有一個對A的引用帮孔,同樣將A的引用減1雷滋,這樣,就完成了循環(huán)引用對象間環(huán)摘除文兢。
這里就會出現(xiàn)一個問題晤斩,如果A引用了B,B沒引用A姆坚,從A出發(fā)刪除B的引用使得B的引用值為0澳泵,導(dǎo)致最后B被回收,如果A還存在兼呵,那么就會導(dǎo)致A指向Bde指針懸空引用兔辅。為了防止這種情況情況的發(fā)生腊敲,python在標記清楚機制中采取了不改動真實引用計數(shù)而只改動引用計數(shù)副本的方法。
eg:
#第一組循環(huán)引用#
a = [1,2]
b = [3,4]
a.append(b)
b.append(a)
#刪除a不刪除b幢妄,b引用a兔仰,此時a和b都不能被回收
del a
#第二組循環(huán)引用#
c = [4,5]
d = [5,6]
c.append(d)
d.append(c)
#刪除c,d cd互相引用,應(yīng)該都被回收
del c
del d
#至此蕉鸳,原a和原c和原d所引用的對象的引用計數(shù)都為1,b所引用的對象的引用計數(shù)為2乎赴,
首先,將對象分為兩撥潮尝,一撥叫root object(存活組)榕吼,一撥叫unreachable(死亡組)。然后勉失,他把各個對象的引用計數(shù)復(fù)制出來羹蚣,對這個副本進行引用環(huán)的摘除。摘除完畢乱凿,此時a的引用計數(shù)的副本是0顽素,b的引用計數(shù)的副本是1,c和d的引用計數(shù)的副本都是0徒蟆。那么先把副本為非0的放到存活組胁出,副本為0的打入死亡組。如果就這樣結(jié)束的話段审,就錯殺了a了全蝶,因為b還要用,我們把a所引用的對象在內(nèi)存中清除了b還能用嗎寺枉?顯然還得在審一遍抑淫,別把無辜的人也給殺了,于是他就在存活組里姥闪,對每個對象都分析一遍始苇,由于目前存活組只有b,那么他只對b分析筐喳,因為b要存活埂蕊,所以b里的元素也要存活,于是在b中就發(fā)現(xiàn)了原a所指向的對象疏唾,于是就把他從死亡組中解救出來蓄氧。至此,進過了一審和二審槐脏,最終把所有的任然在死亡組中的對象通通殺掉喉童,而root object繼續(xù)存活。b所指向的對象引用計數(shù)任然是2,原a所指向的對象的引用計數(shù)仍然是1堂氯。
三蔑担、分代回收
分代的垃圾收集技術(shù)是在上個世紀80年代初發(fā)展起來的一種垃圾收集機制,一系列的研究表明:無論使用何種語言開發(fā)咽白,無論開發(fā)的是何種類型啤握,何種規(guī)模的程序,都存在這樣一點相同之處晶框。即:一定比例的內(nèi)存塊的生存周期都比較短排抬,通常是幾百萬條機器指令的時間,而剩下的內(nèi)存塊授段,起生存周期比較長蹲蒲,甚至?xí)某绦蜷_始一直持續(xù)到程序結(jié)束。
從前面“標記-清除”這樣的垃圾收集機制來看侵贵,這種垃圾收集機制所帶來的額外操作實際上與系統(tǒng)中總的內(nèi)存塊的數(shù)量是相關(guān)的届搁,當(dāng)需要回收的內(nèi)存塊越多時,垃圾檢測帶來的額外操作就越多窍育,而垃圾回收帶來的額外操作就越少卡睦;反之,當(dāng)需回收的內(nèi)存塊越少時漱抓,垃圾檢測就將比垃圾回收帶來更少的額外操作表锻。為了提高垃圾收集的效率,采用“空間換時間的策略”辽旋。
原理:將系統(tǒng)中的所有內(nèi)存塊根據(jù)其存活時間劃分為不同的集合浩嫌,每一個集合就成為一個“代”檐迟,垃圾收集的頻率隨著“代”的存活時間的增大而減小补胚。也就是說,活得越長的對象追迟,就越不可能是垃圾溶其,就應(yīng)該減少對它的垃圾收集頻率。那么如何來衡量這個存活時間:通常是利用幾次垃圾收集動作來衡量敦间,如果一個對象經(jīng)過的垃圾收集次數(shù)越多瓶逃,可以得出:該對象存活時間就越長。