上一篇文章為:→1.3.1import垃圾回收
垃圾回收(二)
1. Garbage collection(GC垃圾回收)
現(xiàn)在的高級(jí)語言如java,c#等升略,都采用了垃圾收集機(jī)制微王,而不再是c,c++里用戶自己管理維護(hù)內(nèi)存的方式品嚣。自己管理內(nèi)存極其自由炕倘,可以任意申請內(nèi)存,但如同一把雙刃劍翰撑,為大量內(nèi)存泄露罩旋,懸空指針等bug埋下隱患。 對于一個(gè)字符串眶诈、列表涨醋、類甚至數(shù)值都是對象,且定位簡單易用的語言逝撬,自然不會(huì)讓用戶去處理如何分配回收內(nèi)存的問題浴骂。 python里也同java一樣采用了垃圾收集機(jī)制,不過不一樣的是: python采用的是引用計(jì)數(shù)機(jī)制為主宪潮,標(biāo)記-清除和分代收集兩種機(jī)制為輔的策略
引用計(jì)數(shù)機(jī)制:
python里每一個(gè)東西都是對象溯警,它們的核心就是一個(gè)結(jié)構(gòu)體:PyObject
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每個(gè)對象必有的內(nèi)容,其中ob_refcnt就是做為引用計(jì)數(shù)狡相。當(dāng)一個(gè)對象有新的引用時(shí)梯轻,它的ob_refcnt就會(huì)增加,當(dāng)引用它的對象被刪除尽棕,它的ob_refcnt就會(huì)減少
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加計(jì)數(shù)
#define Py_DECREF(op) \ //減少計(jì)數(shù)
if (--(op)->ob_refcnt != 0) \
; \
else \
__Py_Dealloc((PyObject *)(op))
當(dāng)引用計(jì)數(shù)為0時(shí)喳挑,該對象生命就結(jié)束了。
引用計(jì)數(shù)機(jī)制的優(yōu)點(diǎn):
- 簡單
- 實(shí)時(shí)性:一旦沒有引用滔悉,內(nèi)存就直接釋放了蟀悦。不用像其他機(jī)制等到特定時(shí)機(jī)。實(shí)時(shí)性還帶來一個(gè)好處:處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí)氧敢。
引用計(jì)數(shù)機(jī)制的缺點(diǎn):
維護(hù)引用計(jì)數(shù)消耗資源
-
循環(huán)引用
list1 = [] list2 = [] list1.append(list2) list2.append(list1)
list1與list2相互引用日戈,如果不存在其他對象對它們的引用,list1與list2的引用計(jì)數(shù)也仍然為1孙乖,所占用的內(nèi)存永遠(yuǎn)無法被回收浙炼,這將是致命的。 對于如今的強(qiáng)大硬件唯袄,缺點(diǎn)1尚可接受弯屈,但是循環(huán)引用導(dǎo)致內(nèi)存泄露,注定python還將引入新的回收機(jī)制恋拷。(標(biāo)記清除和分代收集)
2. 畫說 Ruby 與 Python 垃圾回收
英文原文: visualizing garbage collection in ruby and python
2.1 應(yīng)用程序那顆躍動(dòng)的心
GC系統(tǒng)所承擔(dān)的工作遠(yuǎn)比"垃圾回收"多得多资厉。實(shí)際上,它們負(fù)責(zé)三個(gè)重要任務(wù)蔬顾。它們
為新生成的對象分配內(nèi)存
識(shí)別那些垃圾對象宴偿,并且
從垃圾對象那回收內(nèi)存湘捎。
如果將應(yīng)用程序比作人的身體:所有你所寫的那些優(yōu)雅的代碼,業(yè)務(wù)邏輯窄刘,算法窥妇,應(yīng)該就是大腦。以此類推娩践,垃圾回收機(jī)制應(yīng)該是那個(gè)身體器官呢活翩?(我從RuPy聽眾那聽到了不少有趣的答案:腰子、白血球 :) )
我認(rèn)為垃圾回收就是應(yīng)用程序那顆躍動(dòng)的心翻伺。像心臟為身體其他器官提供血液和營養(yǎng)物那樣材泄,垃圾回收器為你的應(yīng)該程序提供內(nèi)存和對象。如果心臟停跳吨岭,過不了幾秒鐘人就完了脸爱。如果垃圾回收器停止工作或運(yùn)行遲緩,像動(dòng)脈阻塞,你的應(yīng)用程序效率也會(huì)下降,直至最終死掉未妹。
2.2 一個(gè)簡單的例子
運(yùn)用實(shí)例一貫有助于理論的理解簿废。下面是一個(gè)簡單類,分別用Python和Ruby寫成络它,我們今天就以此為例:
順便提一句族檬,兩種語言的代碼竟能如此相像:Ruby 和 Python 在表達(dá)同一事物上真的只是略有不同。但是在這兩種語言的內(nèi)部實(shí)現(xiàn)上是否也如此相似呢化戳?
2.3 Ruby 的對象分配
當(dāng)我們執(zhí)行上面的Node.new(1)時(shí)单料,Ruby到底做了什么?Ruby是如何為我們創(chuàng)建新的對象的呢点楼? 出乎意料的是它做的非常少扫尖。實(shí)際上,早在代碼開始執(zhí)行前掠廓,Ruby就提前創(chuàng)建了成百上千個(gè)對象换怖,并把它們串在鏈表上,名曰:可用列表蟀瞧。下圖所示為可用列表的概念圖:
想象一下每個(gè)白色方格上都標(biāo)著一個(gè)"未使用預(yù)創(chuàng)建對象"沉颂。當(dāng)我們調(diào)用 Node.new ,Ruby只需取一個(gè)預(yù)創(chuàng)建對象給我們使用即可:
上圖中左側(cè)灰格表示我們代碼中使用的當(dāng)前對象,同時(shí)其他白格是未使用對象悦污。(請注意:無疑我的示意圖是對實(shí)際的簡化铸屉。實(shí)際上,Ruby會(huì)用另一個(gè)對象來裝載字符串"ABC",另一個(gè)對象裝載Node類定義切端,還有一個(gè)對象裝載了代碼中分析出的抽象語法樹彻坛,等等)
如果我們再次調(diào)用 Node.new,Ruby將遞給我們另一個(gè)對象:
這個(gè)簡單的用鏈表來預(yù)分配對象的算法已經(jīng)發(fā)明了超過50年,而發(fā)明人這是赫赫有名的計(jì)算機(jī)科學(xué)家John McCarthy昌屉,一開始是用Lisp實(shí)現(xiàn)的钙蒙。Lisp不僅是最早的函數(shù)式編程語言,在計(jì)算機(jī)科學(xué)領(lǐng)域也有許多創(chuàng)舉怠益。其一就是利用垃圾回收機(jī)制自動(dòng)化進(jìn)行程序內(nèi)存管理的概念。
標(biāo)準(zhǔn)版的Ruby瘾婿,也就是眾所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法與McCarthy在1960年的實(shí)現(xiàn)方式很類似蜻牢。無論好壞,Ruby的垃圾回收機(jī)制已經(jīng)53歲高齡了偏陪。像Lisp一樣抢呆,Ruby預(yù)先創(chuàng)建一些對象,然后在你分配新對象或者變量的時(shí)候供你使用笛谦。
2.4 Python 的對象分配
我們已經(jīng)了解了Ruby預(yù)先創(chuàng)建對象并將它們存放在可用列表中抱虐。那Python又怎么樣呢?
盡管由于許多原因Python也使用可用列表(用來回收一些特定對象比如 list)饥脑,但在為新對象和變量分配內(nèi)存的方面Python和Ruby是不同的恳邀。
例如我們用Pyhon來創(chuàng)建一個(gè)Node對象:
與Ruby不同,當(dāng)創(chuàng)建對象時(shí)Python立即向操作系統(tǒng)請求內(nèi)存灶轰。(Python實(shí)際上實(shí)現(xiàn)了一套自己的內(nèi)存分配系統(tǒng)谣沸,在操作系統(tǒng)堆之上提供了一個(gè)抽象層。但是我今天不展開說了笋颤。)
當(dāng)我們創(chuàng)建第二個(gè)對象的時(shí)候乳附,再次像OS請求內(nèi)存:
看起來夠簡單吧,在我們創(chuàng)建對象的時(shí)候伴澄,Python會(huì)花些時(shí)間為我們找到并分配內(nèi)存赋除。
2.5 Ruby 開發(fā)者住在凌亂的房間里
Ruby把無用的對象留在內(nèi)存里,直到下一次GC執(zhí)行
回過來看Ruby非凌。隨著我們創(chuàng)建越來越多的對象举农,Ruby會(huì)持續(xù)尋可用列表里取預(yù)創(chuàng)建對象給我們。因此敞嗡,可用列表會(huì)逐漸變短:
...然后更短:
請注意我一直在為變量n1賦新值并蝗,Ruby把舊值留在原處。"ABC","JKL"和"MNO"三個(gè)Node實(shí)例還滯留在內(nèi)存中秸妥。Ruby不會(huì)立即清除代碼中不再使用的舊對象滚停!Ruby開發(fā)者們就像是住在一間凌亂的房間,地板上摞著衣服粥惧,要么洗碗池里都是臟盤子键畴。作為一個(gè)Ruby程序員,無用的垃圾對象會(huì)一直環(huán)繞著你。
2.6 Python 開發(fā)者住在衛(wèi)生之家庭
用完的垃圾對象會(huì)立即被Python打掃干凈
Python與Ruby的垃圾回收機(jī)制頗為不同起惕。讓我們回到前面提到的三個(gè)Python Node對象:
在內(nèi)部涡贱,創(chuàng)建一個(gè)對象時(shí),Python總是在對象的C結(jié)構(gòu)體里保存一個(gè)整數(shù)惹想,稱為 引用數(shù)
问词。期初,Python將這個(gè)值設(shè)置為1:
值為1說明分別有個(gè)一個(gè)指針指向或是引用這三個(gè)對象嘀粱。假如我們現(xiàn)在創(chuàng)建一個(gè)新的Node實(shí)例激挪,JKL:
與之前一樣,Python設(shè)置JKL的引用數(shù)為1锋叨。然而垄分,請注意由于我們改變了n1指向了JKL,不再指向ABC娃磺,Python就把ABC的引用數(shù)置為0了薄湿。 此刻,Python垃圾回收器立刻挺身而出偷卧!每當(dāng)對象的引用數(shù)減為0豺瘤,Python立即將其釋放,把內(nèi)存還給操作系統(tǒng):
上面Python回收了ABC Node實(shí)例使用的內(nèi)存听诸。記住炉奴,Ruby棄舊對象原地于不顧,也不釋放它們的內(nèi)存蛇更。
Python的這種垃圾回收算法被稱為引用計(jì)數(shù)瞻赶。是George-Collins在1960年發(fā)明的,恰巧與John McCarthy發(fā)明的可用列表算法在同一年出現(xiàn)派任。就像Mike-Bernstein在6月份哥譚市Ruby大會(huì)杰出的垃圾回收機(jī)制演講中說的: "1960年是垃圾收集器的黃金年代..."
Python開發(fā)者工作在衛(wèi)生之家,你可以想象砸逊,有個(gè)患有輕度OCD(一種強(qiáng)迫癥)的室友一刻不停地跟在你身后打掃,你一放下臟碟子或杯子掌逛,有個(gè)家伙已經(jīng)準(zhǔn)備好把它放進(jìn)洗碗機(jī)了师逸!
現(xiàn)在來看第二例子。加入我們讓n2引用n1:
上圖中左邊的DEF的引用數(shù)已經(jīng)被Python減少了豆混,垃圾回收器會(huì)立即回收DEF實(shí)例篓像。同時(shí)JKL的引用數(shù)已經(jīng)變?yōu)榱? ,因?yàn)閚1和n2都指向它皿伺。
2.7 標(biāo)記-清除
最終那間凌亂的房間充斥著垃圾员辩,再不能歲月靜好了。在Ruby程序運(yùn)行了一陣子以后鸵鸥,可用列表最終被用光光了:
此刻所有Ruby預(yù)創(chuàng)建對象都被程序用過了(它們都變灰了)奠滑,可用列表里空空如也(沒有白格子了)丹皱。
此刻Ruby祭出另一McCarthy發(fā)明的算法,名曰:標(biāo)記-清除宋税。首先Ruby把程序停下來摊崭,Ruby用"地球停轉(zhuǎn)垃圾回收大法"。之后Ruby輪詢所有指針杰赛,變量和代碼產(chǎn)生別的引用對象和其他值呢簸。同時(shí)Ruby通過自身的虛擬機(jī)便利內(nèi)部指針。標(biāo)記出這些指針引用的每個(gè)對象乏屯。我在圖中使用M表示根时。
上圖中那三個(gè)被標(biāo)M的對象是程序還在使用的。在內(nèi)部瓶珊,Ruby實(shí)際上使用一串位值啸箫,被稱為:可用位圖(譯注:還記得《編程珠璣》里的為突發(fā)排序嗎耸彪,這對離散度不高的有限整數(shù)集合具有很強(qiáng)的壓縮效果伞芹,用以節(jié)約機(jī)器的資源。)蝉娜,來跟蹤對象是否被標(biāo)記了唱较。
如果說被標(biāo)記的對象是存活的,剩下的未被標(biāo)記的對象只能是垃圾召川,這意味著我們的代碼不再會(huì)使用它了南缓。我會(huì)在下圖中用白格子表示垃圾對象:
接下來Ruby清除這些無用的垃圾對象,把它們送回到可用列表中:
在內(nèi)部這一切發(fā)生得迅雷不及掩耳荧呐,因?yàn)镽uby實(shí)際上不會(huì)吧對象從這拷貝到那汉形。而是通過調(diào)整內(nèi)部指針,將其指向一個(gè)新鏈表的方式倍阐,來將垃圾對象歸位到可用列表中的概疆。
現(xiàn)在等到下回再創(chuàng)建對象的時(shí)候Ruby又可以把這些垃圾對象分給我們使用了妆绞。在Ruby里宿稀,對象們六道輪回,轉(zhuǎn)世投胎篡石,享受多次人生概耻。
2.8 標(biāo)記-刪除 vs. 引用計(jì)數(shù)
乍一看使套,Python的GC算法貌似遠(yuǎn)勝于Ruby的:寧舍潔宇而居穢室乎?為什么Ruby寧愿定期強(qiáng)制程序停止運(yùn)行鞠柄,也不使用Python的算法呢侦高?
然而,引用計(jì)數(shù)并不像第一眼看上去那樣簡單厌杜。有許多原因使得不許多語言不像Python這樣使用引用計(jì)數(shù)GC算法:
首先矫膨,它不好實(shí)現(xiàn)。Python不得不在每個(gè)對象內(nèi)部留一些空間來處理引用數(shù)。這樣付出了一小點(diǎn)兒空間上的代價(jià)侧馅。但更糟糕的是危尿,每個(gè)簡單的操作(像修改變量或引用)都會(huì)變成一個(gè)更復(fù)雜的操作,因?yàn)镻ython需要增加一個(gè)計(jì)數(shù)馁痴,減少另一個(gè)谊娇,還可能釋放對象。
第二點(diǎn)罗晕,它相對較慢济欢。雖然Python隨著程序執(zhí)行GC很穩(wěn)健(一把臟碟子放在洗碗盆里就開始洗啦)小渊,但這并不一定更快法褥。Python不停地更新著眾多引用數(shù)值。特別是當(dāng)你不再使用一個(gè)大數(shù)據(jù)結(jié)構(gòu)的時(shí)候酬屉,比如一個(gè)包含很多元素的列表半等,Python可能必須一次性釋放大量對象。減少引用數(shù)就成了一項(xiàng)復(fù)雜的遞歸過程了呐萨。
最后杀饵,它不是總奏效的。引用計(jì)數(shù)不能處理環(huán)形數(shù)據(jù)結(jié)構(gòu)--也就是含有循環(huán)引用的數(shù)據(jù)結(jié)構(gòu)谬擦。
3. Python中的循環(huán)數(shù)據(jù)結(jié)構(gòu)以及引用計(jì)數(shù)
3.1 循環(huán)引用
通過上篇切距,我們知道在Python中,每個(gè)對象都保存了一個(gè)稱為引用計(jì)數(shù)的整數(shù)值惨远,來追蹤到底有多少引用指向了這個(gè)對象谜悟。無論何時(shí),如果我們程序中的一個(gè)變量或其他對象引用了目標(biāo)對象北秽,Python將會(huì)增加這個(gè)計(jì)數(shù)值葡幸,而當(dāng)程序停止使用這個(gè)對象,則Python會(huì)減少這個(gè)計(jì)數(shù)值羡儿。一旦計(jì)數(shù)值被減到零礼患,Python將會(huì)釋放這個(gè)對象以及回收相關(guān)內(nèi)存空間。
從六十年代開始掠归,計(jì)算機(jī)科學(xué)界就面臨了一個(gè)嚴(yán)重的理論問題缅叠,那就是針對引用計(jì)數(shù)這種算法來說,如果一個(gè)數(shù)據(jù)結(jié)構(gòu)引用了它自身虏冻,即如果這個(gè)數(shù)據(jù)結(jié)構(gòu)是一個(gè)循環(huán)數(shù)據(jù)結(jié)構(gòu)肤粱,那么某些引用計(jì)數(shù)值是肯定無法變成零的。為了更好地理解這個(gè)問題厨相,讓我們舉個(gè)例子领曼。下面的代碼展示了一些上周我們所用到的節(jié)點(diǎn)類:
我們有一個(gè)"構(gòu)造器"(在Python中叫做 init )鸥鹉,在一個(gè)實(shí)例變量中存儲(chǔ)一個(gè)單獨(dú)的屬性。在類定義之后我們創(chuàng)建兩個(gè)節(jié)點(diǎn)庶骄,ABC以及DEF毁渗,在圖中為左邊的矩形框。兩個(gè)節(jié)點(diǎn)的引用計(jì)數(shù)都被初始化為1单刁,因?yàn)楦饔袃蓚€(gè)引用指向各個(gè)節(jié)點(diǎn)(n1和n2)灸异。
現(xiàn)在,讓我們在節(jié)點(diǎn)中定義兩個(gè)附加的屬性羔飞,next以及prev:
跟Ruby不同的是肺樟,Python中你可以在代碼運(yùn)行的時(shí)候動(dòng)態(tài)定義實(shí)例變量或?qū)ο髮傩浴_@看起來似乎有點(diǎn)像Ruby缺失了某些有趣的魔法逻淌。(聲明下我不是一個(gè)Python程序員么伯,所以可能會(huì)存在一些命名方面的錯(cuò)誤)。我們設(shè)置 n1.next 指向 n2卡儒,同時(shí)設(shè)置 n2.prev 指回 n1√锶幔現(xiàn)在,我們的兩個(gè)節(jié)點(diǎn)使用循環(huán)引用的方式構(gòu)成了一個(gè)雙向鏈表
朋贬。同時(shí)請注意到 ABC 以及 DEF 的引用計(jì)數(shù)值已經(jīng)增加到了2凯楔。這里有兩個(gè)指針指向了每個(gè)節(jié)點(diǎn):首先是 n1 以及 n2窜骄,其次就是 next 以及 prev锦募。
現(xiàn)在,假定我們的程序不再使用這兩個(gè)節(jié)點(diǎn)了邻遏,我們將 n1 和 n2 都設(shè)置為null(Python中是None)糠亩。
好了,Python會(huì)像往常一樣將每個(gè)節(jié)點(diǎn)的引用計(jì)數(shù)減少到1准验。
3.2 在Python中的零代(Generation Zero)
請注意在以上剛剛說到的例子中赎线,我們以一個(gè)不是很常見的情況結(jié)尾:我們有一個(gè)“孤島”或是一組未使用的、互相指向的對象糊饱,但是誰都沒有外部引用垂寥。換句話說,我們的程序不再使用這些節(jié)點(diǎn)對象了另锋,所以我們希望Python的垃圾回收機(jī)制能夠足夠智能去釋放這些對象并回收它們占用的內(nèi)存空間滞项。但是這不可能,因?yàn)樗械囊糜?jì)數(shù)都是1而不是0夭坪。Python的引用計(jì)數(shù)算法不能夠處理互相指向自己的對象文判。
這就是為什么Python要引入Generational GC
算法的原因!正如Ruby使用一個(gè)鏈表(free list)來持續(xù)追蹤未使用的室梅、自由的對象一樣戏仓,Python使用一種不同的鏈表來持續(xù)追蹤活躍的對象疚宇。而不將其稱之為“活躍列表”,Python的內(nèi)部C代碼將其稱為零代(Generation Zero)赏殃。每次當(dāng)你創(chuàng)建一個(gè)對象或其他什么值的時(shí)候敷待,Python會(huì)將其加入零代鏈表:
從上邊可以看到當(dāng)我們創(chuàng)建ABC節(jié)點(diǎn)的時(shí)候,Python將其加入零代鏈表仁热。請注意到這并不是一個(gè)真正的列表讼撒,并不能直接在你的代碼中訪問,事實(shí)上這個(gè)鏈表是一個(gè)完全內(nèi)部的Python運(yùn)行時(shí)股耽。 相似的根盒,當(dāng)我們創(chuàng)建DEF節(jié)點(diǎn)的時(shí)候,Python將其加入同樣的鏈表:
現(xiàn)在零代包含了兩個(gè)節(jié)點(diǎn)對象物蝙。(他還將包含Python創(chuàng)建的每個(gè)其他值炎滞,與一些Python自己使用的內(nèi)部值。)
3.3 檢測循環(huán)引用
隨后诬乞,Python會(huì)循環(huán)遍歷零代列表上的每個(gè)對象册赛,檢查列表中每個(gè)互相引用的對象,根據(jù)規(guī)則減掉其引用計(jì)數(shù)震嫉。在這個(gè)過程中森瘪,Python會(huì)一個(gè)接一個(gè)的統(tǒng)計(jì)內(nèi)部引用的數(shù)量以防過早地釋放對象。
為了便于理解票堵,來看一個(gè)例子:
從上面可以看到 ABC 和 DEF 節(jié)點(diǎn)包含的引用數(shù)為1.有三個(gè)其他的對象同時(shí)存在于零代鏈表中扼睬,藍(lán)色的箭頭指示了有一些對象正在被零代鏈表之外的其他對象所引用。(接下來我們會(huì)看到悴势,Python中同時(shí)存在另外兩個(gè)分別被稱為一代和二代的鏈表)窗宇。這些對象有著更高的引用計(jì)數(shù)因?yàn)樗鼈冋诒黄渌羔標(biāo)赶蛑?/p>
接下來你會(huì)看到Python的GC是如何處理零代鏈表的。
通過識(shí)別內(nèi)部引用特纤,Python能夠減少許多零代鏈表對象的引用計(jì)數(shù)军俊。在上圖的第一行中你能夠看見ABC和DEF的引用計(jì)數(shù)已經(jīng)變?yōu)榱懔耍@意味著收集器可以釋放它們并回收內(nèi)存空間了捧存。剩下的活躍的對象則被移動(dòng)到一個(gè)新的鏈表:一代鏈表粪躬。
從某種意義上說,Python的GC算法類似于Ruby所用的標(biāo)記回收算法昔穴。周期性地從一個(gè)對象到另一個(gè)對象追蹤引用以確定對象是否還是活躍的镰官,正在被程序所使用的,這正類似于Ruby的標(biāo)記過程傻咖。
Python中的GC閾值
Python什么時(shí)候會(huì)進(jìn)行這個(gè)標(biāo)記過程朋魔?隨著你的程序運(yùn)行,Python解釋器保持對新創(chuàng)建的對象卿操,以及因?yàn)橐糜?jì)數(shù)為零而被釋放掉的對象的追蹤警检。從理論上說孙援,這兩個(gè)值應(yīng)該保持一致,因?yàn)槌绦蛐陆ǖ拿總€(gè)對象都應(yīng)該最終被釋放掉扇雕。
當(dāng)然拓售,事實(shí)并非如此。因?yàn)檠h(huán)引用的原因镶奉,并且因?yàn)槟愕某绦蚴褂昧艘恍┍绕渌麑ο蟠嬖跁r(shí)間更長的對象础淤,從而被分配對象的計(jì)數(shù)值與被釋放對象的計(jì)數(shù)值之間的差異在逐漸增長。一旦這個(gè)差異累計(jì)超過某個(gè)閾值哨苛,則Python的收集機(jī)制就啟動(dòng)了鸽凶,并且觸發(fā)上邊所說到的零代算法,釋放“浮動(dòng)的垃圾”建峭,并且將剩下的對象移動(dòng)到一代列表玻侥。
隨著時(shí)間的推移,程序所使用的對象逐漸從零代列表移動(dòng)到一代列表亿蒸。而Python對于一代列表中對象的處理遵循同樣的方法凑兰,一旦被分配計(jì)數(shù)值與被釋放計(jì)數(shù)值累計(jì)到達(dá)一定閾值,Python會(huì)將剩下的活躍對象移動(dòng)到二代列表边锁。
通過這種方法姑食,你的代碼所長期使用的對象,那些你的代碼持續(xù)訪問的活躍對象茅坛,會(huì)從零代鏈表轉(zhuǎn)移到一代再轉(zhuǎn)移到二代音半。通過不同的閾值設(shè)置,Python可以在不同的時(shí)間間隔處理這些對象灰蛙。Python處理零代最為頻繁祟剔,其次是一代然后才是二代隔躲。
弱代假說
來看看代垃圾回收算法的核心行為:垃圾回收器會(huì)更頻繁的處理新對象摩梧。一個(gè)新的對象即是你的程序剛剛創(chuàng)建的,而一個(gè)來的對象則是經(jīng)過了幾個(gè)時(shí)間周期之后仍然存在的對象宣旱。Python會(huì)在當(dāng)一個(gè)對象從零代移動(dòng)到一代仅父,或是從一代移動(dòng)到二代的過程中提升(promote)這個(gè)對象。
為什么要這么做浑吟?這種算法的根源來自于弱代假說(weak generational hypothesis)笙纤。這個(gè)假說由兩個(gè)觀點(diǎn)構(gòu)成:首先是年親的對象通常死得也快,而老對象則很有可能存活更長的時(shí)間组力。
假定現(xiàn)在我用Python或是Ruby創(chuàng)建一個(gè)新對象:
根據(jù)假說省容,我的代碼很可能僅僅會(huì)使用ABC很短的時(shí)間。這個(gè)對象也許僅僅只是一個(gè)方法中的中間結(jié)果燎字,并且隨著方法的返回這個(gè)對象就將變成垃圾了腥椒。大部分的新對象都是如此般地很快變成垃圾阿宅。然而,偶爾程序會(huì)創(chuàng)建一些很重要的笼蛛,存活時(shí)間比較長的對象-例如web應(yīng)用中的session變量或是配置項(xiàng)洒放。
通過頻繁的處理零代鏈表中的新對象,Python的垃圾收集器將把時(shí)間花在更有意義的地方:它處理那些很快就可能變成垃圾的新對象滨砍。同時(shí)只在很少的時(shí)候往湿,當(dāng)滿足閾值的條件,收集器才回去處理那些老變量惋戏。