Python 垃圾回收機(jī)制與原理解析

來源:https://www.biaodianfu.com/garbage-collector.html
Python 作為一門解釋型語言,以代碼簡潔易懂著稱。我們可以直接對名稱賦值,而不必聲明類型。名稱類型的確定、內(nèi)存空間的分配與釋放都是由 Python 解釋器在運(yùn)行時(shí)進(jìn)行的。
Python 這一自動(dòng)管理內(nèi)存功能極大的減小了程序員負(fù)擔(dān)。對于 Python 這種高級別的語言,開發(fā)者完成可以不用關(guān)心其內(nèi)部的垃圾回收機(jī)制。相輔相成的通過學(xué)習(xí) Python 內(nèi)部的垃圾回收機(jī)制,并了解其原理,可以使得開發(fā)者能夠更好的寫代碼,更加 Pythonista。
目錄
Python內(nèi)存管理機(jī)制
Python的垃圾回收機(jī)制
????????2.3 分代回收(Generational garbage collector)
Python中的gc模塊
Python內(nèi)存管理機(jī)制
在 Python 中,內(nèi)存管理涉及到一個(gè)包含所有 Python 對象和數(shù)據(jù)結(jié)構(gòu)的私有堆(heap)。這個(gè)私有堆的管理由內(nèi)部的 Python 內(nèi)存管理器(Python memory manager) 保證。Python 內(nèi)存管理器有不同的組件來處理各種動(dòng)態(tài)存儲管理方面的問題,如共享、分割、預(yù)分配或緩存。
在最底層,一個(gè)原始內(nèi)存分配器通過與操作系統(tǒng)的內(nèi)存管理器交互,確保私有堆中有足夠的空間來存儲所有與 Python 相關(guān)的數(shù)據(jù)。在原始內(nèi)存分配器的基礎(chǔ)上,幾個(gè)對象特定的分配器在同一堆上運(yùn)行,并根據(jù)每種對象類型的特點(diǎn)實(shí)現(xiàn)不同的內(nèi)存管理策略。例如,整數(shù)對象在堆內(nèi)的管理方式不同于字符串、元組或字典,因?yàn)檎麛?shù)需要不同的存儲需求和速度與空間的權(quán)衡。因此,Python 內(nèi)存管理器將一些工作分配給對象特定分配器,但確保后者在私有堆的范圍內(nèi)運(yùn)行。
Python 堆內(nèi)存的管理是由解釋器來執(zhí)行,用戶對它沒有控制權(quán),即使他們經(jīng)常操作指向堆內(nèi)內(nèi)存塊的對象指針,理解這一點(diǎn)十分重要。

Python為了避免對于小對象(<=512bytes)出現(xiàn)數(shù)量過多的GC,導(dǎo)致的性能消耗。Python對于小對象采用子分配 (內(nèi)存池) 的方式進(jìn)行內(nèi)存塊的管理。對于大對象使用標(biāo)準(zhǔn)C中的allocator來分配內(nèi)存。
Python對于小對象的allocator由大到小分為三個(gè)層級:arena、pool、block。
Block
Block是最小的一個(gè)層級,每個(gè)block僅僅可以容納包含一個(gè)固定大小的Python Object。大小從8-512bytes,以8bytes為步長,分為64類不同的block。
| Request in bytes | Size of allocated block | size class idx |
|---|---|---|
| 1-8 | 8 | 0 |
| 9-16 | 16 | 1 |
| 17-24 | 24 | 2 |
| 25-32 | 32 | 3 |
| 33-40 | 40 | 4 |
| 41-48 | 48 | 5 |
| … | … | … |
| 505-512 | 512 | 63 |
Pool
Pool具有相同大小的block組成集合稱為Pool。通常情況下,Pool的大小為4kb,與虛擬內(nèi)存頁的大小保存一致。限制Pool中block有固定的大小,有如下好處是: 當(dāng)一個(gè)對象在當(dāng)前Pool中的某個(gè)block被銷毀時(shí),Pool內(nèi)存管理可以將新生成的對象放入該block中。
/*?Pool?for?small?blocks.?*/
struct?pool_header?{
????union?{?block?*_padding;
????????????uint?count;?}?ref;??????????/*?number?of?allocated?blocks????*/
????block?*freeblock;???????????????????/*?pool's?free?list?head?????????*/
????struct?pool_header?*nextpool;???????/*?next?pool?of?this?size?class??*/
????struct?pool_header?*prevpool;???????/*?previous?pool???????""????????*/
????uint?arenaindex;????????????????????/*?index?into?arenas?of?base?adr?*/
????uint?szidx;?????????????????????????/*?block?size?class?index????????*/
????uint?nextoffset;????????????????????/*?bytes?to?virgin?block?????????*/
????uint?maxnextoffset;?????????????????/*?largest?valid?nextoffset??????*/
};
具有相同大小的Pool通過雙向鏈表來連接,sidx用來標(biāo)識Block的類型。arenaindex標(biāo)識當(dāng)前Pool屬于哪個(gè)Arena。ref.conut標(biāo)識當(dāng)前Pool使用了多少個(gè)Block。freeblock:標(biāo)識當(dāng)前Pools中可用block的指針。freeblock實(shí)際是單鏈表實(shí)現(xiàn),當(dāng)一塊block為空狀態(tài)時(shí),則將該block插入到freeblock鏈表的頭部。
每個(gè)Pool都有三個(gè)狀態(tài):
used:部分使用,即Pool不滿,也不為空 full:滿,即所有Pool中的Block都已被分配 empty:空, 即所有Pool中的Block都未被分配
usedpool為了很好的高效的管理Pool,Python額外使用了array,usedpool來管理。即如下圖所示,usedpool按序存儲著每個(gè)特性大小的Pool的頭指針,相同大小的Pool按照雙向鏈表來連接。當(dāng)分配新的內(nèi)存空間時(shí),創(chuàng)建一個(gè)特定大小的Pool,只需要使用usedpools找到頭指針,遍歷即可,當(dāng)沒有內(nèi)存空間時(shí),只需要在Pool的雙向鏈表的頭部插入新的Pool即可。

Arena
Pools和Blocks都不會直接去進(jìn)行內(nèi)存分配(allocate), Pools和Blocks會使用從arena那邊已經(jīng)分配好的內(nèi)存空間。Arena:是分配在堆上256kb的塊狀內(nèi)存,提供了64個(gè)Pool。

struct?arena_object?{
????uintptr_t?address;
????block*?pool_address;
????uint?nfreepools;
????uint?ntotalpools;
????struct?pool_header*?freepools;
????struct?arena_object*?nextarena;
????struct?arena_object*?prevarena;
};
所有的arenas也使用雙鏈表進(jìn)行連接(prevarena, nextarena字段). nfreepools和ntotalpools存儲著當(dāng)前可用pools的信息。freepools指針指向當(dāng)前可用的pools。arena結(jié)構(gòu)簡單,職責(zé)即為按需給pools分配內(nèi)存,當(dāng)一個(gè)arena為空時(shí),則將該arena的內(nèi)存歸還給操作系統(tǒng)。
Python的垃圾回收機(jī)制
Python采用的是引用計(jì)數(shù)機(jī)制為主,標(biāo)記-清除和分代收集兩種機(jī)制為輔的策略。
引用計(jì)數(shù)(reference counting)
Python語言默認(rèn)采用的垃圾收集機(jī)制是“引用計(jì)數(shù)法 Reference Counting”,該算法最早George E. Collins在1960的時(shí)候首次提出,50年后的今天,該算法依然被很多編程語言使用。
引用計(jì)數(shù)法的原理是:每個(gè)對象維護(hù)一個(gè)ob_ref字段,用來記錄該對象當(dāng)前被引用的次數(shù),每當(dāng)新的引用指向該對象時(shí),它的引用計(jì)數(shù)ob_ref加1,每當(dāng)該對象的引用失效時(shí)計(jì)數(shù)ob_ref減1,一旦對象的引用計(jì)數(shù)為0,該對象立即被回收,對象占用的內(nèi)存空間將被釋放。
它的缺點(diǎn)是需要額外的空間維護(hù)引用計(jì)數(shù),這個(gè)問題是其次的,不過最主要的問題是它不能解決對象的“循環(huán)引用”,因此,也有很多語言比如Java并沒有采用該算法做來垃圾的收集機(jī)制。
Python中一切皆對象,也就是說,在Python中你用到的一切變量,本質(zhì)上都是類對象。實(shí)際上每一個(gè)對象的核心就是一個(gè)結(jié)構(gòu)體PyObject,它的內(nèi)部有一個(gè)引用計(jì)數(shù)器ob_refcnt,程序在運(yùn)行的過程中會實(shí)時(shí)的更新ob_refcnt的值,來反映引用當(dāng)前對象的名稱數(shù)量。當(dāng)某對象的引用計(jì)數(shù)值為0,說明這個(gè)對象變成了垃圾,那么它會被回收掉,它所用的內(nèi)存也會被立即釋放掉。
typedef?struct?_object?{
????int?ob_refcnt;//引用計(jì)數(shù)
????struct?_typeobject?*ob_type;
}?PyObject;
導(dǎo)致引用計(jì)數(shù)+1的情況:
對象被創(chuàng)建,例如a=23 對象被引用,例如b=a 對象被作為參數(shù),傳入到一個(gè)函數(shù)中,例如func(a) 對象作為一個(gè)元素,存儲在容器中,例如list1=[a,a]
導(dǎo)致引用計(jì)數(shù)-1的情況
對象的別名被顯式銷毀,例如del a 對象的別名被賦予新的對象,例如a=24 一個(gè)對象離開它的作用域,例如f函數(shù)執(zhí)行完畢時(shí),func函數(shù)中的局部變量(全局變量不會) 對象所在的容器被銷毀,或從容器中刪除對象
我們可以通過sys包中的getrefcount()來獲取一個(gè)名稱所引用的對象當(dāng)前的引用計(jì)。sys.getrefcount()本身會使得引用計(jì)數(shù)加一.
循環(huán)引用
引用計(jì)數(shù)的另一個(gè)現(xiàn)象就是循環(huán)引用,相當(dāng)于有兩個(gè)對象a和b,其中a引用了b,b引用了a,這樣a和b的引用計(jì)數(shù)都為1,并且永遠(yuǎn)都不會為0,這就意味著這兩個(gè)對象永遠(yuǎn)都不會被回收了,這就是循環(huán)引用 , a與b形成了一個(gè)引用循環(huán) , 示例如下 :
a?=?[1,?2]??#?計(jì)數(shù)為?1
b?=?[2,?3]??#?計(jì)數(shù)為?1
a.append(b)??#?計(jì)數(shù)為?2
b.append(a)??#?計(jì)數(shù)為?2
del?a??#?計(jì)數(shù)為?1
del?b??#?計(jì)數(shù)為?1
除了上述兩個(gè)對象互相引用之外 , 還可以引用自身 :
list3?=?[1,2,3]
list3.append(list3)
循環(huán)引用導(dǎo)致變量計(jì)數(shù)永不為 0,造成引用計(jì)數(shù)無法將其刪除。
引用計(jì)數(shù)法有其明顯的優(yōu)點(diǎn),如高效、實(shí)現(xiàn)邏輯簡單、具備實(shí)時(shí)性,一旦一個(gè)對象的引用計(jì)數(shù)歸零,內(nèi)存就直接釋放了。不用像其他機(jī)制等到特定時(shí)機(jī)。將垃圾回收隨機(jī)分配到運(yùn)行的階段,處理回收內(nèi)存的時(shí)間分?jǐn)偟搅似綍r(shí),正常程序的運(yùn)行比較平穩(wěn)。引用計(jì)數(shù)也存在著一些缺點(diǎn):
邏輯簡單,但實(shí)現(xiàn)有些麻煩。每個(gè)對象需要分配單獨(dú)的空間來統(tǒng)計(jì)引用計(jì)數(shù),這無形中加大的空間的負(fù)擔(dān),并且需要對引用計(jì)數(shù)進(jìn)行維護(hù),在維護(hù)的時(shí)候很容易會出錯(cuò)。 在一些場景下,可能會比較慢。正常來說垃圾回收會比較平穩(wěn)運(yùn)行,但是當(dāng)需要釋放一個(gè)大的對象時(shí),比如字典,需要對引用的所有對象循環(huán)嵌套調(diào)用,從而可能會花費(fèi)比較長的時(shí)間。 循環(huán)引用。這將是引用計(jì)數(shù)的致命傷,引用計(jì)數(shù)對此是無解的,因此必須要使用其它的垃圾回收算法對其進(jìn)行補(bǔ)充。
也就是說,Python 的垃圾回收機(jī)制,很大一部分是為了處理可能產(chǎn)生的循環(huán)引用,是對引用計(jì)數(shù)的補(bǔ)充。
標(biāo)記清除(Mark and Sweep)
Python采用了“標(biāo)記-清除”(Mark and Sweep)算法,解決容器對象可能產(chǎn)生的循環(huán)引用問題。(注意,只有容器對象才會產(chǎn)生循環(huán)引用的情況,比如列表、字典、用戶自定義類的對象、元組等。而像數(shù)字,字符串這類簡單類型不會出現(xiàn)循環(huán)引用。作為一種優(yōu)化策略,對于只包含簡單類型的元組也不在標(biāo)記清除算法的考慮之列)
跟其名稱一樣,該算法在進(jìn)行垃圾回收時(shí)分成了兩步,分別是:
標(biāo)記階段,遍歷所有的對象,如果是可達(dá)的(reachable),也就是還有對象引用它,那么就標(biāo)記該對象為可達(dá); 清除階段,再次遍歷對象,如果發(fā)現(xiàn)某個(gè)對象沒有標(biāo)記為可達(dá),則就將其回收。
對象之間會通過引用(指針)連在一起,構(gòu)成一個(gè)有向圖,對象構(gòu)成這個(gè)有向圖的節(jié)點(diǎn),而引用關(guān)系構(gòu)成這個(gè)有向圖的邊。從root object出發(fā),沿著有向邊遍歷對象,可達(dá)的(reachable)對象標(biāo)記為活動(dòng)對象,不可達(dá)(unreachable)的對象就是要被清除的非活動(dòng)對象。所謂 root object,就是一些全局變量、調(diào)用棧、寄存器,這些對象是不可被刪除的。

我們把小黑圈視為 root object,從小黑圈出發(fā),對象 1 可達(dá),那么它將被標(biāo)記,對象 2、3可間接可達(dá)也會被標(biāo)記,而 4 和 5 不可達(dá),那么 1、2、3 就是活動(dòng)對象,4 和 5 是非活動(dòng)對象會被 GC 回收。
如下圖所示,在標(biāo)記清除算法中,為了追蹤容器對象,需要每個(gè)容器對象維護(hù)兩個(gè)額外的指針,用來將容器對象組成一個(gè)雙端鏈表,指針分別指向前后兩個(gè)容器對象,方便插入和刪除操作。Python解釋器(Cpython)維護(hù)了兩個(gè)這樣的雙端鏈表,一個(gè)鏈表存放著需要被掃描的容器對象,另一個(gè)鏈表存放著臨時(shí)不可達(dá)對象。
在圖中,這兩個(gè)鏈表分別被命名為“Object to Scan”和“Unreachable”。圖中例子是這么一個(gè)情況:link1,link2,link3組成了一個(gè)引用環(huán),同時(shí)link1還被一個(gè)變量A(其實(shí)這里稱為名稱A更好)引用。link4自引用,也構(gòu)成了一個(gè)引用環(huán)。
從圖中我們還可以看到,每一個(gè)節(jié)點(diǎn)除了有一個(gè)記錄當(dāng)前引用計(jì)數(shù)的變量ref_count還有一個(gè)gc_ref變量,這個(gè)gc_ref是ref_count的一個(gè)副本,所以初始值為ref_count的大小。

gc啟動(dòng)的時(shí)候,會逐個(gè)遍歷“Object to Scan”鏈表中的容器對象,并且將當(dāng)前對象所引用的所有對象的gc_ref減一。(掃描到link1的時(shí)候,由于link1引用了link2,所以會將link2的gc_ref減一,接著掃描link2,由于link2引用了link3,所以會將link3的gc_ref減一…..)像這樣將“Objects to Scan”鏈表中的所有對象考察一遍之后,兩個(gè)鏈表中的對象的ref_count和gc_ref的情況如下圖所示。這一步操作就相當(dāng)于解除了循環(huán)引用對引用計(jì)數(shù)的影響。

接著,gc會再次掃描所有的容器對象,如果對象的gc_ref值為0,那么這個(gè)對象就被標(biāo)記為GC_TENTATIVELY_UNREACHABLE,并且被移至“Unreachable”鏈表中。下圖中的link3和link4就是這樣一種情況。

如果對象的gc_ref不為0,那么這個(gè)對象就會被標(biāo)記為GC_REACHABLE。同時(shí)當(dāng)gc發(fā)現(xiàn)有一個(gè)節(jié)點(diǎn)是可達(dá)的,那么他會遞歸式的將從該節(jié)點(diǎn)出發(fā)可以到達(dá)的所有節(jié)點(diǎn)標(biāo)記為GC_REACHABLE,這就是下圖中l(wèi)ink2和link3所碰到的情形。

除了將所有可達(dá)節(jié)點(diǎn)標(biāo)記為GC_REACHABLE之外,如果該節(jié)點(diǎn)當(dāng)前在“Unreachable”鏈表中的話,還需要將其移回到“Object to Scan”鏈表中,下圖就是link3移回之后的情形。

第二次遍歷的所有對象都遍歷完成之后,存在于“Unreachable”鏈表中的對象就是真正需要被釋放的對象。如上圖所示,此時(shí)link4存在于Unreachable鏈表中,gc隨即釋放之。
上面描述的垃圾回收的階段,會暫停整個(gè)應(yīng)用程序,等待標(biāo)記清除結(jié)束后才會恢復(fù)應(yīng)用程序的運(yùn)行。
標(biāo)記清除的優(yōu)點(diǎn)在于可以解決循環(huán)引用的問題,并且在整個(gè)算法執(zhí)行的過程中沒有額外的開銷。缺點(diǎn)在于當(dāng)執(zhí)行標(biāo)記清除時(shí)正常的程序?qū)蛔枞A硗庖粋€(gè)缺點(diǎn)在于,標(biāo)記清除算法在執(zhí)行很多次數(shù)后,程序的堆空間會產(chǎn)生一些小的內(nèi)存碎片。
分代回收(Generational garbage collector)
分代回收技術(shù)是上個(gè)世紀(jì)80年代初發(fā)展起來的一種垃圾回收機(jī)制,也是Java 垃圾回收的核心算法。分代回收是基于這樣的一個(gè)統(tǒng)計(jì)事實(shí),對于程序,存在一定比例的內(nèi)存塊的生存周期比較短;而剩下的內(nèi)存塊,生存周期會比較長,甚至?xí)某绦蜷_始一直持續(xù)到程序結(jié)束。
生存期較短對象的比例通常在 80%~90% 之間。因此,簡單地認(rèn)為:對象存在時(shí)間越長,越可能不是垃圾,應(yīng)該越少去收集。這樣在執(zhí)行標(biāo)記-清除算法時(shí)可以有效減小遍歷的對象數(shù),從而提高垃圾回收的速度,是一種以空間換時(shí)間的方法策略。
Python 將所有的對象分為 0,1,2 三代; 所有的新建對象都是 0 代對象; 當(dāng)某一代對象經(jīng)歷過垃圾回收,依然存活,就被歸入下一代對象。
那么,按什么標(biāo)準(zhǔn)劃分對象呢?是否隨機(jī)將一個(gè)對象劃分到某個(gè)代即可呢?答案是否定的。實(shí)際上,對象分代里頭也是有不少學(xué)問的,好的劃分標(biāo)準(zhǔn)可顯著提升垃圾回收的效率。

Python 內(nèi)部根據(jù)對象存活時(shí)間,將對象分為 3 代,每個(gè)代都由一個(gè) gc_generation 結(jié)構(gòu)體來維護(hù)(定義于 Include/internal/mem.h):
struct?gc_generation?{?
????PyGC_Head?head;?
????int?threshold;?/*?collection?threshold?*/?
????int?count;?/*?count?of?allocations?or?collections?of?younger?generations?*/?
};
其中:
head,可收集對象鏈表頭部,代中的對象通過該鏈表維護(hù) threshold,僅當(dāng) count 超過本閥值時(shí),Python 垃圾回收操作才會掃描本代對象 count,計(jì)數(shù)器,不同代統(tǒng)計(jì)項(xiàng)目不一樣
Python 虛擬機(jī)運(yùn)行時(shí)狀態(tài)由 Include/internal/pystate.h 中的 pyruntimestate 結(jié)構(gòu)體表示,它內(nèi)部有一個(gè) _gc_runtime_state ( Include/internal/mem.h )結(jié)構(gòu)體,保存 GC 狀態(tài)信息,包括 3 個(gè)對象代。這 3 個(gè)代,在 GC 模塊( Modules/gcmodule.c ) _PyGC_Initialize 函數(shù)中初始化:
struct?gc_generation?generations[NUM_GENERATIONS]?=?{
????/*?PyGC_Head,?threshold,?count?*/
????{{{_GEN_HEAD(0),?_GEN_HEAD(0),?0}},?700?0},
????{{{_GEN_HEAD(1),?_GEN_HEAD(1),?0}},?10,?0},
????{{{_GEN_HEAD(2),?_GEN_HEAD(2),?0}},?10,?0},
};
為方便討論,我們將這 3 個(gè)代分別稱為:初生代、中生代 以及 老生代。當(dāng)這 3 個(gè)代初始化完畢后,對應(yīng)的 gc_generation 數(shù)組大概是這樣的:

每個(gè) gc_generation 結(jié)構(gòu)體鏈表頭節(jié)點(diǎn)都指向自己,換句話說每個(gè)可收集對象鏈表一開始都是空的,計(jì)數(shù)器字段 count 都被初始化為 0,而閥值字段 threshold 則有各自的策略。這些策略如何理解呢?
Python 調(diào)用 _PyObject_GC_Alloc 為需要跟蹤的對象分配內(nèi)存時(shí),該函數(shù)將初生代 count 計(jì)數(shù)器加1,隨后對象將接入初生代對象鏈表,當(dāng) Python 調(diào)用 PyObject_GC_Del 釋放垃圾對象內(nèi)存時(shí),該函數(shù)將初生代 count 計(jì)數(shù)器,1,_PyObject_GC_Alloc 自增 count 后如果超過閥值(700),將調(diào)用 collect_generations 執(zhí)行一次垃圾回收( GC )。 collect_generations 函數(shù)從老生代開始,逐個(gè)遍歷每個(gè)生代,找出需要執(zhí)行回收操作(,count>threshold )的最老生代。隨后調(diào)用 collect_with_callback 函數(shù)開始回收該生代,而該函數(shù)最終調(diào)用 collect 函數(shù)。 collect 函數(shù)處理某個(gè)生代時(shí),先將比它年輕的生代計(jì)數(shù)器 count 重置為 0,然后將它們的對象鏈表移除,與自己的拼接在一起后執(zhí)行 GC 算法,最后將下一個(gè)生代計(jì)數(shù)器加1。
于是:
系統(tǒng)每新增 701 個(gè)需要 GC 的對象,Python 就執(zhí)行一次 GC 操作 每次 GC 操作需要處理的生代可能是不同的,由 count 和 threshold 共同決定 某個(gè)生代需要執(zhí)行 GC ( count>hreshold ),在它前面的所有年輕生代也同時(shí)執(zhí)行 GC 對多個(gè)代執(zhí)行 GC,Python 將它們的對象鏈表拼接在一起,一次性處理 GC 執(zhí)行完畢后,count 清零,而后一個(gè)生代 count 加 1
下面是一個(gè)簡單的例子:初生代觸發(fā) GC 操作,Python 執(zhí)行 collect_generations 函數(shù)。它找出了達(dá)到閥值的最老生代是中生代,因此調(diào)用 collection_with_callback(1),1 是中生代在數(shù)組中的下標(biāo)。

collection_with_callback(1) 最終執(zhí)調(diào)用 collect(1) ,它先將后一個(gè)生代計(jì)數(shù)器加一;然后將本生代以及前面所有年輕生代計(jì)數(shù)器重置為零;最后調(diào)用 gc_list_merge 將這幾個(gè)可回收對象鏈表合并在一起:

最后,collect 函數(shù)執(zhí)行標(biāo)記清除算法,對合并后的鏈表進(jìn)行垃圾回收。

這就是分代回收機(jī)制的全部秘密,它看似復(fù)雜,但只需略加總結(jié)就可以得到幾條直白的策略:
每新增 701 個(gè)需要 GC 的對象,觸發(fā)一次新生代 GC 每執(zhí)行 11 次新生代 GC ,觸發(fā)一次中生代 GC 每執(zhí)行 11 次中生代 GC ,觸發(fā)一次老生代 GC (老生代 GC 還受其他策略影響,頻率更低) 執(zhí)行某個(gè)生代 GC 前,年輕生代對象鏈表也移入該代,一起 GC 一個(gè)對象創(chuàng)建后,隨著時(shí)間推移將被逐步移入老生代,回收頻率逐漸降低
Python 中的 gc 模塊
gc 模塊是我們在Python中進(jìn)行內(nèi)存管理的接口,一般情況Python程序員都不用關(guān)心自己程序的內(nèi)存管理問題,但是有的時(shí)候,比如發(fā)現(xiàn)自己程序存在內(nèi)存泄露,就可能需要用到gc模塊的接口來排查問題。
有的 Python 系統(tǒng)會關(guān)閉自動(dòng)垃圾回收,程序自己判斷回收的時(shí)機(jī),據(jù)說 instagram 的系統(tǒng)就是這樣做的,整體運(yùn)行效率提高了10%。
常用函數(shù):
set_debug(flags) :設(shè)置gc的debug日志,一般設(shè)置為gc.DEBUG_LEAK可以看到內(nèi)存泄漏的對象。 collect([generation]) :執(zhí)行垃圾回收。會將那些有循環(huán)引用的對象給回收了。這個(gè)函數(shù)可以傳遞參數(shù),0代表只回收第0代的的垃圾對象、1代表回收第0代和第1代的對象,2代表回收第0、1、2代的對象。如果不傳參數(shù),那么會使用2作為默認(rèn)參數(shù)。 get_threshold() :獲取gc模塊執(zhí)行垃圾回收的閾值。返回的是個(gè)元組,第0個(gè)是零代的閾值,第1個(gè)是1代的閾值,第2個(gè)是2代的閾值。 set_threshold(threshold0[, threshold1[, threshold2]) :設(shè)置執(zhí)行垃圾回收的閾值。 get_count() :獲取當(dāng)前自動(dòng)執(zhí)行垃圾回收的計(jì)數(shù)器。返回一個(gè)元組。第0個(gè)是零代的垃圾對象的數(shù)量,第1個(gè)是零代鏈表遍歷的次數(shù),第2個(gè)是1代鏈表遍歷的次數(shù)。
參考鏈接:
聊聊Python內(nèi)存管理 (https://andrewpqc.github.io/2018/10/08/python-memory-management/) gc — 垃圾回收器接口 (https://docs.python.org/zh-cn/3/library/gc.html)

還不過癮?試試它們
▲Python 微服務(wù)框架 Nameko 初體驗(yàn)
▲如何用 Docker 構(gòu)建企業(yè)級 PyPi 服務(wù)?
