? ? ? ?一些高級語言,比如Java、C#都會有垃圾回收機(jī)制,防止一些沒有的空間占用過多的內(nèi)存,最后導(dǎo)致程序宕掉,c,c++里用戶自己管理維護(hù)內(nèi)存的方式,內(nèi)存的申請、釋放需要用戶手動操作。Python底層也制定了垃圾回收機(jī)制,因此普通用戶在使用Python時,不用關(guān)心何時開啟、執(zhí)行垃圾回收機(jī)制。
Python的垃圾回收機(jī)制主要采用的是引用計(jì)數(shù)為主、標(biāo)記清除與隔代回收為輔的垃圾回收策略。
小整數(shù)對象池
整數(shù)在程序的使用中會非常頻繁,因此Python在程序運(yùn)行之前,會在內(nèi)存中創(chuàng)建出小整數(shù)對象池供用戶使用,防止頻繁操作整數(shù)對象進(jìn)行內(nèi)存的申請與銷毀。
Python小整數(shù)對象池的范圍是[-5, 256],這些整數(shù)對象都是提前創(chuàng)建好的,在這個范圍內(nèi)的整數(shù)獨(dú)占內(nèi)存,不會被垃圾回收。


超過這個范圍,Python就會創(chuàng)建新的對象,開辟新的內(nèi)存空間:

intern機(jī)制
a = "HelloPython"
b = "HelloPython"
c = "HelloPython"
類似這樣的,Python去不會創(chuàng)建3個字符串對象,而是會默認(rèn)開啟intern機(jī)制,將這三個對象指向同一個內(nèi)存空間,節(jié)省內(nèi)存開銷。

但是如果字符串中存在特殊字符,情況會有例外,intern機(jī)制就會失效:

引用計(jì)數(shù)機(jī)制
Python中,每一個東西都是對象,底層實(shí)現(xiàn)中都是一個PyObject:

其中,ob_refcnt屬性記錄著該對象的引用次數(shù),當(dāng)有東西引用該對象時,該屬性會加1,引用該對象被刪除時,該屬性會減1,如果引用次數(shù)為0,就會觸發(fā)垃圾回收機(jī)制

引用計(jì)數(shù)機(jī)制的優(yōu)點(diǎn):簡單 實(shí)時性,一旦沒有引用,內(nèi)存就直接釋放了。不用像其他機(jī)制等到特定時機(jī)。實(shí)時性還帶來一個好處:處理回收內(nèi)存的時間分?jǐn)偟搅似綍r。
引用計(jì)數(shù)機(jī)制的缺點(diǎn):引用計(jì)數(shù)耗費(fèi)資源;出現(xiàn)循環(huán)引用時,會出現(xiàn)問題


t1與t2形成了循環(huán)引用,刪除t1與t2之后,仍有引用計(jì)數(shù),會導(dǎo)致內(nèi)存空間不斷被占用,進(jìn)而導(dǎo)致宕機(jī)
為了解決這一問題,Python又引入了標(biāo)記清除與隔代回收機(jī)制
標(biāo)記清除機(jī)制
請注意在以上剛剛說到的例子中,我們以一個不是很常見的情況結(jié)尾:我們有一個“孤島”或是一組未使用的、互相指向的對象,但是誰都沒有外部引用。換句話說,我們的程序不再使用這些節(jié)點(diǎn)對象了,所以我們希望Python的垃圾回收機(jī)制能夠足夠智能去釋放這些對象并回收它們占用的內(nèi)存空間。但是這不可能,因?yàn)樗械囊糜?jì)數(shù)都是1而不是0。Python的引用計(jì)數(shù)算法不能夠處理互相指向自己的對象。
“標(biāo)記-清除”就是為了解決循環(huán)引用的問題??梢园渌麑ο笠玫娜萜鲗ο螅ū热纾簂ist,set,dict,class,instance)都可能產(chǎn)生循環(huán)引用。
我們必須承認(rèn)上面的事實(shí),如果兩個對象的引用計(jì)數(shù)都為1,但是僅僅存在他們之間的循環(huán)引用,那么這兩個對象都是需要被回收的,也就是說,它們的引用計(jì)數(shù)雖然表現(xiàn)為非0,但實(shí)際上有效的引用計(jì)數(shù)為0。我們必須先將循環(huán)引用摘掉,那么這兩個對象的有效計(jì)數(shù)就現(xiàn)身了。假設(shè)兩個對象為A、B,我們從A出發(fā),因?yàn)樗幸粋€對B的引用,則將B的引用計(jì)數(shù)減1;然后順著引用達(dá)到B,因?yàn)锽有一個對A的引用,同樣將A的引用減1,這樣,就完成了循環(huán)引用對象間環(huán)摘除。
但是這樣就有一個問題,假設(shè)對象A有一個對象引用C,而C沒有引用A,如果將C計(jì)數(shù)引用減1,而最后A并沒有被回收,顯然,我們錯誤的將C的引用計(jì)數(shù)減1,這將導(dǎo)致在未來的某個時刻出現(xiàn)一個對C的懸空引用。這就要求我們必須在A沒有被刪除的情況下復(fù)原C的引用計(jì)數(shù),如果采用這樣的方案,那么維護(hù)引用計(jì)數(shù)的復(fù)雜度將成倍增加。
原理:“標(biāo)記-清除”采用了更好的做法,我們并不改動真實(shí)的引用計(jì)數(shù),而是將集合中對象的引用計(jì)數(shù)復(fù)制一份副本,改動該對象引用的副本。對于副本做任何的改動,都不會影響到對象生命走起的維護(hù)。
這個計(jì)數(shù)副本的唯一作用是尋找root object集合(該集合中的對象是不能被回收的)。當(dāng)成功尋找到root object集合之后,首先將現(xiàn)在的內(nèi)存鏈表一分為二,一條鏈表中維護(hù)root object集合,成為root鏈表,而另外一條鏈表中維護(hù)剩下的對象,成為unreachable鏈表。之所以要剖成兩個鏈表,是基于這樣的一種考慮:現(xiàn)在的unreachable可能存在被root鏈表中的對象,直接或間接引用的對象,這些對象是不能被回收的,一旦在標(biāo)記的過程中,發(fā)現(xiàn)這樣的對象,就將其從unreachable鏈表中移到root鏈表中;當(dāng)完成標(biāo)記后,unreachable鏈表中剩下的所有對象就是名副其實(shí)的垃圾對象了,接下來的垃圾回收只需限制在unreachable鏈表中即可。
那么GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?
對象之間通過引用(指針)連在一起,構(gòu)成一個有向圖,對象構(gòu)成這個有向圖的節(jié)點(diǎn),而引用關(guān)系構(gòu)成這個有向圖的邊。從根對象(root object)出發(fā),沿著有向邊遍歷對象,可達(dá)的(reachable)對象標(biāo)記為活動對象,不可達(dá)的對象就是要被清除的非活動對象。根對象就是全局變量、調(diào)用棧、寄存器。
隔代回收機(jī)制
Python使用一種不同的鏈表來持續(xù)追蹤活躍的對象。而不將其稱之為“活躍列表”,Python的內(nèi)部C代碼將其稱為零代(Generation Zero)。每次當(dāng)你創(chuàng)建一個對象或其他什么值的時候,Python會將其加入零代鏈表:

從上邊可以看到當(dāng)我們創(chuàng)建ABC節(jié)點(diǎn)的時候,Python將其加入零代鏈表。請注意到這并不是一個真正的列表,并不能直接在你的代碼中訪問,事實(shí)上這個鏈表是一個完全內(nèi)部的Python運(yùn)行時。 相似的,當(dāng)我們創(chuàng)建DEF節(jié)點(diǎn)的時候,Python將其加入同樣的鏈表:

現(xiàn)在零代包含了兩個節(jié)點(diǎn)對象。(他還將包含Python創(chuàng)建的每個其他值,與一些Python自己使用的內(nèi)部值。)
檢測循環(huán)引用
隨后,Python會循環(huán)遍歷零代列表上的每個對象,檢查列表中每個互相引用的對象,根據(jù)規(guī)則減掉其引用計(jì)數(shù)。在這個過程中,Python會一個接一個的統(tǒng)計(jì)內(nèi)部引用的數(shù)量以防過早地釋放對象。
為了便于理解,來看一個例子:

從上面可以看到 ABC 和 DEF 節(jié)點(diǎn)包含的引用數(shù)為1.有三個其他的對象同時存在于零代鏈表中,藍(lán)色的箭頭指示了有一些對象正在被零代鏈表之外的其他對象所引用。(接下來我們會看到,Python中同時存在另外兩個分別被稱為一代和二代的鏈表)。這些對象有著更高的引用計(jì)數(shù)因?yàn)樗鼈冋诒黄渌羔標(biāo)赶蛑?/p>
接下來你會看到Python的GC是如何處理零代鏈表的。

導(dǎo)致引用計(jì)數(shù)+1的情況
1、對象被創(chuàng)建,例如a=23?
2、對象被引用,例如b=a?
3、對象被作為參數(shù),傳入到一個函數(shù)中,例如func(a)?
4、對象作為一個元素,存儲在容器中,例如list1=[a,a]
導(dǎo)致引用計(jì)數(shù)-1的情況
1、對象的別名被顯式銷毀,例如del a?
2、對象的別名被賦予新的對象,例如a=24?
3、一個對象離開它的作用域,例如f函數(shù)執(zhí)行完畢時,func函數(shù)中的局部變量(全局變量不會)?
4、對象所在的容器被銷毀,或從容器中刪除對象
GC常用函數(shù):
1、gc.set_debug(flags)設(shè)置gc的debug日志,一般設(shè)置為gc.DEBUG_LEAK
2、gc.collect([generation])顯式進(jìn)行垃圾回收,可以輸入?yún)?shù),0代表只檢查第一代的對象,1代表檢查一,二代的對象,2代表檢查一,二,三代的對象,如果不傳參數(shù),執(zhí)行一個full collection,也就是等于傳2。 返回不可達(dá)(unreachable objects)對象的數(shù)目
3、gc.get_threshold()獲取的gc模塊中自動執(zhí)行垃圾回收的頻率。
4、gc.set_threshold(threshold0[, threshold1[, threshold2])設(shè)置自動執(zhí)行垃圾回收的頻率。
5、gc.get_count()獲取當(dāng)前自動執(zhí)行垃圾回收的計(jì)數(shù)器,返回一個長度為3的列表