轉(zhuǎn)自:https://juejin.im/post/5b34b117f265da59a50b2fbe,作者:

Python垃圾回收(GC)三層心法,你了解到第幾層?
垃圾回收機(jī)制應(yīng)該是面試最常問(wèn)的問(wèn)題了,那么Python中的垃圾回收機(jī)制(Garbage Collection)是怎么解決的呢?我記得每一本python入門(mén)的書(shū)籍都會(huì)說(shuō)python中請(qǐng)不要擔(dān)心內(nèi)存泄漏這個(gè) 問(wèn)題,那么這個(gè)背后又是什么原理,今天就來(lái)818。
Python中的GC算法
分為下三點(diǎn):引用計(jì)數(shù)/標(biāo)記-清除/分代回收
·引用計(jì)數(shù)(主要)
剛開(kāi)始學(xué)習(xí)Python的時(shí)候總是會(huì)有人告訴你,萬(wàn)物皆對(duì)象是一大特色。在Python中每一個(gè)對(duì)象的核心就是一個(gè)結(jié)構(gòu)體PyObject,它的內(nèi)部有一個(gè)引用計(jì)數(shù)器(ob_refcnt)。

引用計(jì)數(shù)的意思就是,一個(gè)對(duì)象在它剛被New出來(lái)呱呱(gugu不是guagua)墜地的時(shí)候因?yàn)楸籒ew方法引用了所以他的引用計(jì)數(shù)就是1,如果它被引用(也就是在之前的基礎(chǔ)上 例如:b=a,被丟入函數(shù)列表等等被引用就會(huì)在引用計(jì)數(shù)上加1),如果引用它的對(duì)象被刪除的時(shí)候(在之前的基礎(chǔ)上DEL b)那么它的引用計(jì)數(shù)就會(huì)減少一一直到當(dāng)它的引用計(jì)數(shù)變?yōu)?的時(shí)候,垃圾回收機(jī)制就會(huì)找上門(mén)做掉它(回收),腦補(bǔ)一下 :開(kāi)門(mén)我是查水表的。
優(yōu)點(diǎn)/缺點(diǎn):
因?yàn)橐糜?jì)數(shù)是GC主要方法,來(lái)看一下優(yōu)缺點(diǎn)。
優(yōu):
簡(jiǎn)單,實(shí)時(shí)性(一旦為零就不跟你多BB,做掉)
缺:
·?維護(hù)性高(簡(jiǎn)單實(shí)時(shí),但是額外占用了一部分資源,雖然邏輯簡(jiǎn)單,但是麻煩。好比你吃草莓,吃一次洗一下手,而不是吃完洗手。)
·?不能解決的情況:--->循環(huán)引用(如下):

說(shuō)實(shí)話感覺(jué)還有點(diǎn)像死鎖的問(wèn)題,這種問(wèn)題出現(xiàn)在可以循環(huán)的結(jié)構(gòu)中List Dict Object等等,如上代碼a、b間的引用都為1,而a、b被引用的對(duì)象刪除后都各自減去1(所以他們各自的引用計(jì)數(shù)還是1),這時(shí)候就尷尬了啊,都是1就有了免死金牌(一直是1不會(huì)變化了)。這樣的情況單單靠引用計(jì)數(shù)就無(wú)法解決了。?也為我們引入了下面的主題 標(biāo)記-清除
·標(biāo)記-清除:?標(biāo)記清除就是用來(lái)解決循環(huán)引用的問(wèn)題的只有容器對(duì)象才會(huì)出現(xiàn)引用循環(huán),比如列表、字典、類(lèi)、元組。 首先,為了追蹤容器對(duì)象,需要每個(gè)容器對(duì)象維護(hù)兩個(gè)額外的指針, 用來(lái)將容器對(duì)象組成一個(gè)鏈表,指針?lè)謩e指向前后兩個(gè)容器對(duì)象,方便插入和刪除操作。試想一下,現(xiàn)在有兩種情況:
A:

B:

Okay,現(xiàn)在開(kāi)始說(shuō)正題。在標(biāo)記-清除算法中,有兩個(gè)集中營(yíng),一個(gè)是root鏈表(root object),另外一個(gè)是unreachable鏈表。
·?對(duì)于情景A,原來(lái)再未執(zhí)行DEL語(yǔ)句的時(shí)候,a,b的引用計(jì)數(shù)都為2(init+append=2),但是在DEL執(zhí)行完以后,a,b引用次數(shù)互相減1。a,b陷入循環(huán)引用的圈子中,然后標(biāo)記-清除算法開(kāi)始出來(lái)做事,找到其中一端a,開(kāi)始拆這個(gè)a,b的引用環(huán)(我們從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)摘除。),去掉以后發(fā)現(xiàn),a,b循環(huán)引用變?yōu)榱?,所以a,b就被處理到unreachable鏈表中直接被做掉。
·?對(duì)于情景B,簡(jiǎn)單一看那b取環(huán)后引用計(jì)數(shù)還為1,但是a取環(huán),就為0了。這個(gè)時(shí)候a已經(jīng)進(jìn)入unreachable鏈表中,已經(jīng)被判為死刑了,但是這個(gè)時(shí)候,root鏈表中有b。如果a被做掉,那世界上還有什么正義...?,在root鏈表中的b會(huì)被進(jìn)行引用檢測(cè)引用了a,如果a被做掉了,那么b就...涼涼,一審?fù)晔?,二審a無(wú)罪,所以被拉到了root鏈表中。
QA:?為什么要搞這兩個(gè)鏈表
之所以要剖成兩個(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鏈表中即可。
分代回收:
了解分類(lèi)回收,首先要了解一下,GC的閾值,所謂閾值就是一個(gè)臨界點(diǎn)的值。隨著你的程序運(yùn)行,Python解釋器保持對(duì)新創(chuàng)建的對(duì)象,以及因?yàn)橐糜?jì)數(shù)為零而被釋放掉的對(duì)象的追蹤。從理論上說(shuō),創(chuàng)建==釋放數(shù)量應(yīng)該是這樣子。但是如果存在循環(huán)引用的話,肯定是創(chuàng)建>釋放數(shù)量,當(dāng)創(chuàng)建數(shù)與釋放數(shù)量的差值達(dá)到規(guī)定的閾值的時(shí)候,當(dāng)當(dāng)當(dāng)當(dāng)~分代回收機(jī)制就登場(chǎng)啦。
垃圾回收=垃圾檢測(cè)+釋放。
分代回收思想將對(duì)象分為三代(generation 0,1,2),0代表幼年對(duì)象,1代表青年對(duì)象,2代表老年對(duì)象。根據(jù)弱代假說(shuō)(越年輕的對(duì)象越容易死掉,老的對(duì)象通常會(huì)存活更久。)?新生的對(duì)象被放入0代,如果該對(duì)象在第0代的一次gc垃圾回收中活了下來(lái),那么它就被放到第1代里面(它就升級(jí)了)。如果第1代里面的對(duì)象在第1代的一次gc垃圾回收中活了下來(lái),它就被放到第2代里面。gc.set_threshold(threshold0[,threshold1[,threshold2]])設(shè)置gc每一代垃圾回收所觸發(fā)的閾值。從上一次第0代gc后,如果分配對(duì)象的個(gè)數(shù)減去釋放對(duì)象的個(gè)數(shù)大于threshold0,那么就會(huì)對(duì)第0代中的對(duì)象進(jìn)行g(shù)c垃圾回收檢查。?從上一次第1代gc后,如過(guò)第0代被gc垃圾回收的次數(shù)大于threshold1,那么就會(huì)對(duì)第1代中的對(duì)象進(jìn)行g(shù)c垃圾回收檢查。同樣,從上一次第2代gc后,如過(guò)第1代被gc垃圾回收的次數(shù)大于threshold2,那么就會(huì)對(duì)第2代中的對(duì)象進(jìn)行g(shù)c垃圾回收檢查。
這算是簡(jiǎn)單的講完了Python的GC,打完收工。