天天看點

Python垃圾回收機制

一、了解整數對象池及 intern機制

1. 小整數對象池

整數在程式中的使用非常廣泛,Python為了優化速度,使用了小整數對象池, 避免為整數頻繁申請和銷毀記憶體空間。

Python 對小整數的定義是 [-5, 257) 這些整數對象是提前建立好的,不會被垃圾回收。在一個 Python 的程式中,所有位于這個範圍内的整數使用的都是同一個對象.

同理,單個字母也是這樣的。

但是當定義2個相同的字元串時,引用計數為0,觸發垃圾回收

2. 大整數對象池

終端是每次執行一次,是以每次的大整數都重新建立,而在pycharm中,每次運作是所有代碼都加載都記憶體中,屬于一個整體,是以

這個時候會有一個大整數對象池,即處于一個代碼塊的大整數是同一個對象

Python垃圾回收機制

3. intern機制

a1 = "HelloWorld"
a2 = "HelloWorld"
a3 = "HelloWorld"
a4 = "HelloWorld"
a5 = "HelloWorld"
a6 = "HelloWorld"
a7 = "HelloWorld"
a8 = "HelloWorld"
a9 = "HelloWorld"
      

python會不會建立9個對象呢?在記憶體中會不會開辟9個”HelloWorld”的記憶體空間呢? 想一下,如果是這樣的話,我們寫10000個對象,比如a1=”HelloWorld”…..a1000=”HelloWorld”, 那他豈不是開辟了1000個”HelloWorld”所占的記憶體空間了呢?如果真這樣,記憶體不就爆了嗎?是以python中有這樣一個機制——

intern機制

,讓他隻占用一個”HelloWorld”所占的記憶體空間。靠引用計數去維護何時釋放。  

Python垃圾回收機制

總結:

1.小整數[-5,257)共用對象,常駐記憶體

2.單個字元共用對象,常駐記憶體

3.單個單詞,不可修改,預設開啟intern機制,共用對象,引用計數為0,則銷毀,代碼示例如下:

Python垃圾回收機制

4.字元串(含有空格),不可修改,沒開啟intern機制,不共用對象,引用計數為0,銷毀代碼示例如下:

Python垃圾回收機制

5.大整數同一代碼塊共享記憶體,否則不共用記憶體,引用計數為0,銷毀

Python垃圾回收機制

6.數值類型和字元串類型在 Python 中都是不可變的,這意味着你無法修改這個對象的值,每次對變量的修改,實際上是建立一個新的對象

Python垃圾回收機制

二、Garbage collection(GC垃圾回收)

現在的進階語言如java,c#等,都采用了垃圾收集機制,而不再是c,c++裡使用者自己管理維護記憶體的方式。自己管理記憶體極其自由,可以任意申請記憶體,但如同一把雙刃劍,為大量記憶體洩露,懸空指針等bug埋下隐患。 對于一個字元串、清單、類甚至數值都是對象,且定位簡單易用的語言,自然不會讓使用者去處理如何配置設定回收記憶體的問題。 python裡也同java一樣采用了垃圾收集機制,不過不一樣的是: python采用的是引用計數機制為主,标記-清除和分代收集兩種機制為輔的政策

引用計數機制:

python裡每一個東西都是對象,它們的核心就是一個結構體:

PyObject

typedef struct_object {
    int ob_refcnt;
    struct_typeobject *ob_type;
} PyObject;       

PyObject是每個對象必有的内容,其中ob_refcnt就是做為引用計數。當一個對象有新的引用時,它的ob_refcnt就會增加,當引用它的對象被删除,它的ob_refcnt就會減少

#define Py_INCREF(op)   ((op)->ob_refcnt++) //增加計數
#define Py_DECREF(op) \ //減少計數
    if (--(op)->ob_refcnt != 0) \
        ; \
    else \
        __Py_Dealloc((PyObject *)(op))      

當引用計數為0時,該對象生命就結束了。

引用計數機制的優點:

  • 簡單
  • 實時性:一旦沒有引用,記憶體就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收記憶體的時間分攤到了平時。

引用計數機制的缺點:

  • 維護引用計數消耗資源
  • 循環引用
    list1 = []
    list2 = []
    list1.append(list2)
    list2.append(list1)        

list1與list2互相引用,如果不存在其他對象對它們的引用,list1與list2的引用計數也仍然為1,所占用的記憶體永遠無法被回收,這将是緻命的。 對于如今的強大硬體,缺點1尚可接受,但是循環引用導緻記憶體洩露,注定python還将引入新的回收機制。(标記清除和分代收集)

2. 畫說 Ruby 與 Python 垃圾回收

英文原文: visualizing garbage collection in ruby and python

2.1 應用程式那顆躍動的心

GC系統所承擔的工作遠比"垃圾回收"多得多。實際上,它們負責三個重要任務。它們

為新生成的對象配置設定記憶體
識别那些垃圾對象,并且
從垃圾對象那回收記憶體。        

如果将應用程式比作人的身體:所有你所寫的那些優雅的代碼,業務邏輯,算法,應該就是大腦。以此類推,垃圾回收機制應該是那個身體器官呢?(我從RuPy聽衆那聽到了不少有趣的答案:腰子、白血球 :) )

我認為垃圾回收就是應用程式那顆躍動的心。像心髒為身體其他器官提供血液和營養物那樣,垃圾回收器為你的應該程式提供記憶體和對象。如果心髒停跳,過不了幾秒鐘人就完了。如果垃圾回收器停止工作或運作遲緩,像動脈阻塞,你的應用程式效率也會下降,直至最終死掉。

2.2 一個簡單的例子

運用執行個體一貫有助于理論的了解。下面是一個簡單類,分别用Python和Ruby寫成,我們今天就以此為例:

Python垃圾回收機制

順便提一句,兩種語言的代碼竟能如此相像:Ruby 和 Python 在表達同一事物上真的隻是略有不同。但是在這兩種語言的内部實作上是否也如此相似呢?

2.3 Ruby 的對象配置設定

當我們執行上面的Node.new(1)時,Ruby到底做了什麼?Ruby是如何為我們建立新的對象的呢? 出乎意料的是它做的非常少。實際上,早在代碼開始執行前,Ruby就提前建立了成百上千個對象,并把它們串在連結清單上,名曰:可用清單。下圖所示為可用清單的概念圖:

Python垃圾回收機制

想象一下每個白色方格上都标着一個"未使用預建立對象"。當我們調用 Node.new ,Ruby隻需取一個預建立對象給我們使用即可:

Python垃圾回收機制

上圖中左側灰格表示我們代碼中使用的目前對象,同時其他白格是未使用對象。(請注意:無疑我的示意圖是對實際的簡化。實際上,Ruby會用另一個對象來裝載字元串"ABC",另一個對象裝載Node類定義,還有一個對象裝載了代碼中分析出的抽象文法樹,等等)

如果我們再次調用 Node.new,Ruby将遞給我們另一個對象:

Python垃圾回收機制

這個簡單的用連結清單來預配置設定對象的算法已經發明了超過50年,而發明人這是赫赫有名的計算機科學家John McCarthy,一開始是用Lisp實作的。Lisp不僅是最早的函數式程式設計語言,在計算機科學領域也有許多創舉。其一就是利用垃圾回收機制自動化進行程式記憶體管理的概念。

标準版的Ruby,也就是衆所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法與McCarthy在1960年的實作方式很類似。無論好壞,Ruby的垃圾回收機制已經53歲高齡了。像Lisp一樣,Ruby預先建立一些對象,然後在你配置設定新對象或者變量的時候供你使用。

2.4 Python 的對象配置設定

我們已經了解了Ruby預先建立對象并将它們存放在可用清單中。那Python又怎麼樣呢?

盡管由于許多原因Python也使用可用清單(用來回收一些特定對象比如 list),但在為新對象和變量配置設定記憶體的方面Python和Ruby是不同的。

例如我們用Pyhon來建立一個Node對象:

Python垃圾回收機制

與Ruby不同,當建立對象時Python立即向作業系統請求記憶體。(Python實際上實作了一套自己的記憶體配置設定系統,在作業系統堆之上提供了一個抽象層。但是我今天不展開說了。)

當我們建立第二個對象的時候,再次像OS請求記憶體:

Python垃圾回收機制

看起來夠簡單吧,在我們建立對象的時候,Python會花些時間為我們找到并配置設定記憶體。

2.5 Ruby 開發者住在淩亂的房間裡

Python垃圾回收機制

Ruby把無用的對象留在記憶體裡,直到下一次GC執行

回過來看Ruby。随着我們建立越來越多的對象,Ruby會持續尋可用清單裡取預建立對象給我們。是以,可用清單會逐漸變短:

Python垃圾回收機制

 ...然後更短:

Python垃圾回收機制

請注意我一直在為變量n1賦新值,Ruby把舊值留在原處。"ABC","JKL"和"MNO"三個Node執行個體還滞留在記憶體中。Ruby不會立即清除代碼中不再使用的舊對象!Ruby開發者們就像是住在一間淩亂的房間,地闆上摞着衣服,要麼洗碗池裡都是髒盤子。作為一個Ruby程式員,無用的垃圾對象會一直環繞着你。

2.6 Python 開發者住在衛生之家庭

Python垃圾回收機制

用完的垃圾對象會立即被Python打掃幹淨

Python與Ruby的垃圾回收機制頗為不同。讓我們回到前面提到的三個Python Node對象:

Python垃圾回收機制

在内部,建立一個對象時,Python總是在對象的C結構體裡儲存一個整數,稱為 

引用數

。期初,Python将這個值設定為1:

Python垃圾回收機制

值為1說明分别有個一個指針指向或是引用這三個對象。假如我們現在建立一個新的Node執行個體,JKL:

Python垃圾回收機制

與之前一樣,Python設定JKL的引用數為1。然而,請注意由于我們改變了n1指向了JKL,不再指向ABC,Python就把ABC的引用數置為0了。 此刻,Python垃圾回收器立刻挺身而出!每當對象的引用數減為0,Python立即将其釋放,把記憶體還給作業系統:

Python垃圾回收機制

上面Python回收了ABC Node執行個體使用的記憶體。記住,Ruby棄舊對象原地于不顧,也不釋放它們的記憶體。

Python的這種垃圾回收算法被稱為引用計數。是George-Collins在1960年發明的,恰巧與John McCarthy發明的可用清單算法在同一年出現。就像Mike-Bernstein在6月份哥譚市Ruby大會傑出的垃圾回收機制演講中說的: "1960年是垃圾收集器的黃金年代..."

Python開發者工作在衛生之家,你可以想象,有個患有輕度OCD(一種強迫症)的室友一刻不停地跟在你身後打掃,你一放下髒碟子或杯子,有個家夥已經準備好把它放進洗碗機了!

現在來看第二例子。加入我們讓n2引用n1:

Python垃圾回收機制

上圖中左邊的DEF的引用數已經被Python減少了,垃圾回收器會立即回收DEF執行個體。同時JKL的引用數已經變為了2 ,因為n1和n2都指向它。

2.7 标記-清除

最終那間淩亂的房間充斥着垃圾,再不能歲月靜好了。在Ruby程式運作了一陣子以後,可用清單最終被用光光了:

Python垃圾回收機制

此刻所有Ruby預建立對象都被程式用過了(它們都變灰了),可用清單裡空空如也(沒有白格子了)。

此刻Ruby祭出另一McCarthy發明的算法,名曰:标記-清除。首先Ruby把程式停下來,Ruby用"地球停轉垃圾回收大法"。之後Ruby輪詢所有指針,變量和代碼産生别的引用對象和其他值。同時Ruby通過自身的虛拟機便利内部指針。标記出這些指針引用的每個對象。我在圖中使用M表示。

Python垃圾回收機制

上圖中那三個被标M的對象是程式還在使用的。在内部,Ruby實際上使用一串位值,被稱為:可用位圖(譯注:還記得《程式設計珠玑》裡的為突發排序嗎,這對離散度不高的有限整數集合具有很強的壓縮效果,用以節約機器的資源。),來跟蹤對象是否被标記了。

Python垃圾回收機制

如果說被标記的對象是存活的,剩下的未被标記的對象隻能是垃圾,這意味着我們的代碼不再會使用它了。我會在下圖中用白格子表示垃圾對象:

Python垃圾回收機制

接下來Ruby清除這些無用的垃圾對象,把它們送回到可用清單中:

Python垃圾回收機制

在内部這一切發生得迅雷不及掩耳,因為Ruby實際上不會吧對象從這拷貝到那。而是通過調整内部指針,将其指向一個新連結清單的方式,來将垃圾對象歸位到可用清單中的。

現在等到下回再建立對象的時候Ruby又可以把這些垃圾對象分給我們使用了。在Ruby裡,對象們六道輪回,轉世投胎,享受多次人生。

2.8 标記-删除 vs 引用計數

乍一看,Python的GC算法貌似遠勝于Ruby的:甯舍潔宇而居穢室乎?為什麼Ruby甯願定期強制程式停止運作,也不使用Python的算法呢?

然而,引用計數并不像第一眼看上去那樣簡單。有許多原因使得不許多語言不像Python這樣使用引用計數GC算法:

首先,它不好實作。Python不得不在每個對象内部留一些空間來處理引用數。這樣付出了一小點兒空間上的代價。但更糟糕的是,每個簡單的操作(像修改變量或引用)都會變成一個更複雜的操作,因為Python需要增加一個計數,減少另一個,還可能釋放對象。

第二點,它相對較慢。雖然Python随着程式執行GC很穩健(一把髒碟子放在洗碗盆裡就開始洗啦),但這并不一定更快。Python不停地更新着衆多引用數值。特别是當你不再使用一個大資料結構的時候,比如一個包含很多元素的清單,Python可能必須一次性釋放大量對象。減少引用數就成了一項複雜的遞歸過程了。

最後,它不是總奏效的。引用計數不能處理環形資料結構--也就是含有循環引用的資料結構。        

三、 Python中的循環資料結構以及引用計數

1 循環引用

通過上篇,我們知道在Python中,每個對象都儲存了一個稱為引用計數的整數值,來追蹤到底有多少引用指向了這個對象。無論何時,如果我們程式中的一個變量或其他對象引用了目标對象,Python将會增加這個計數值,而當程式停止使用這個對象,則Python會減少這個計數值。一旦計數值被減到零,Python将會釋放這個對象以及回收相關記憶體空間。

從六十年代開始,計算機科學界就面臨了一個嚴重的理論問題,那就是針對引用計數這種算法來說,如果一個資料結構引用了它自身,即如果這個資料結構是一個循環資料結構,那麼某些引用計數值是肯定無法變成零的。為了更好地了解這個問題,讓我們舉個例子。下面的代碼展示了一些上周我們所用到的節點類:

Python垃圾回收機制

我們有一個"構造器"(在Python中叫做 __init__ ),在一個執行個體變量中存儲一個單獨的屬性。在類定義之後我們建立兩個節點,ABC以及DEF,在圖中為左邊的矩形框。兩個節點的引用計數都被初始化為1,因為各有兩個引用指向各個節點(n1和n2)。

現在,讓我們在節點中定義兩個附加的屬性,next以及prev:

Python垃圾回收機制

跟Ruby不同的是,Python中你可以在代碼運作的時候動态定義執行個體變量或對象屬性。這看起來似乎有點像Ruby缺失了某些有趣的魔法。(聲明下我不是一個Python程式員,是以可能會存在一些命名方面的錯誤)。我們設定 n1.next 指向 n2,同時設定 n2.prev 指回 n1。現在,我們的兩個節點使用循環引用的方式構成了一個

雙向連結清單

。同時請注意到 ABC 以及 DEF 的引用計數值已經增加到了2。這裡有兩個指針指向了每個節點:首先是 n1 以及 n2,其次就是 next 以及 prev。

現在,假定我們的程式不再使用這兩個節點了,我們将 n1 和 n2 都設定為null(Python中是None)。

Python垃圾回收機制

好了,Python會像往常一樣将每個節點的引用計數減少到1。

2 在Python中的零代(Generation Zero)

請注意在以上剛剛說到的例子中,我們以一個不是很常見的情況結尾:我們有一個“孤島”或是一組未使用的、互相指向的對象,但是誰都沒有外部引用。換句話說,我們的程式不再使用這些節點對象了,是以我們希望Python的垃圾回收機制能夠足夠智能去釋放這些對象并回收它們占用的記憶體空間。但是這不可能,因為所有的引用計數都是1而不是0。Python的引用計數算法不能夠處理互相指向自己的對象。

這就是為什麼Python要引入

Generational GC

算法的原因!正如Ruby使用一個連結清單(free list)來持續追蹤未使用的、自由的對象一樣,Python使用一種不同的連結清單來持續追蹤活躍的對象。而不将其稱之為“活躍清單”,Python的内部C代碼将其稱為零代(Generation Zero)。每次當你建立一個對象或其他什麼值的時候,Python會将其加入零代連結清單:

Python垃圾回收機制

從上邊可以看到當我們建立ABC節點的時候,Python将其加入零代連結清單。請注意到這并不是一個真正的清單,并不能直接在你的代碼中通路,事實上這個連結清單是一個完全内部的Python運作時。 相似的,當我們建立DEF節點的時候,Python将其加入同樣的連結清單:

Python垃圾回收機制

現在零代包含了兩個節點對象。(他還将包含Python建立的每個其他值,與一些Python自己使用的内部值。)

3.3 檢測循環引用

随後,Python會循環周遊零代清單上的每個對象,檢查清單中每個互相引用的對象,根據規則減掉其引用計數。在這個過程中,Python會一個接一個的統計内部引用的數量以防過早地釋放對象。

為了便于了解,來看一個例子:

Python垃圾回收機制

從上面可以看到 ABC 和 DEF 節點包含的引用數為1.有三個其他的對象同時存在于零代連結清單中,藍色的箭頭訓示了有一些對象正在被零代連結清單之外的其他對象所引用。(接下來我們會看到,Python中同時存在另外兩個分别被稱為一代和二代的連結清單)。這些對象有着更高的引用計數因為它們正在被其他指針所指向着。

接下來你會看到Python的GC是如何處理零代連結清單的。

Python垃圾回收機制

通過識别内部引用,Python能夠減少許多零代連結清單對象的引用計數。在上圖的第一行中你能夠看見ABC和DEF的引用計數已經變為零了,這意味着收集器可以釋放它們并回收記憶體空間了。剩下的活躍的對象則被移動到一個新的連結清單:一代連結清單。

從某種意義上說,Python的GC算法類似于Ruby所用的标記回收算法。周期性地從一個對象到另一個對象追蹤引用以确定對象是否還是活躍的,正在被程式所使用的,這正類似于Ruby的标記過程。

Python中的GC門檻值

Python什麼時候會進行這個标記過程?随着你的程式運作,Python解釋器保持對新建立的對象,以及因為引用計數為零而被釋放掉的對象的追蹤。從理論上說,這兩個值應該保持一緻,因為程式建立的每個對象都應該最終被釋放掉。

當然,事實并非如此。因為循環引用的原因,并且因為你的程式使用了一些比其他對象存在時間更長的對象,進而被配置設定對象的計數值與被釋放對象的計數值之間的差異在逐漸增長。一旦這個差異累計超過某個門檻值,則Python的收集機制就啟動了,并且觸發上邊所說到的零代算法,釋放“浮動的垃圾”,并且将剩下的對象移動到一代清單。

随着時間的推移,程式所使用的對象逐漸從零代清單移動到一代清單。而Python對于一代清單中對象的處理遵循同樣的方法,一旦被配置設定計數值與被釋放計數值累計到達一定門檻值,Python會将剩下的活躍對象移動到二代清單。

通過這種方法,你的代碼所長期使用的對象,那些你的代碼持續通路的活躍對象,會從零代連結清單轉移到一代再轉移到二代。通過不同的門檻值設定,Python可以在不同的時間間隔處理這些對象。Python處理零代最為頻繁,其次是一代然後才是二代。

來看看代垃圾回收算法的核心行為:垃圾回收器會更頻繁的處理新對象。一個新的對象即是你的程式剛剛建立的,而一個舊的對象則是經過了幾個時間周期之後仍然存在的對象。Python會在當一個對象從零代移動到一代,或是從一代移動到二代的過程中提升(promote)這個對象。

為什麼要這麼做?這種算法的根源來自于弱代假說(weak generational hypothesis)。這個假說由兩個觀點構成:首先是年親的對象通常死得也快,而老對象則很有可能存活更長的時間。

假定現在我用Python或是Ruby建立一個新對象:

Python垃圾回收機制

根據假說,我的代碼很可能僅僅會使用ABC很短的時間。這個對象也許僅僅隻是一個方法中的中間結果,并且随着方法的傳回這個對象就将變成垃圾了。大部分的新對象都是如此般地很快變成垃圾。然而,偶爾程式會建立一些很重要的,存活時間比較長的對象-例如web應用中的session變量或是配置項。

通過頻繁的處理零代連結清單中的新對象,Python的垃圾收集器将把時間花在更有意義的地方:它處理那些很快就可能變成垃圾的新對象。同時隻在很少的時候,當滿足門檻值的條件,收集器才回去處理那些老變量。

四、gc子產品

1.垃圾回收機制

Python中的垃圾回收是以引用計數為主,分代收集為輔。
      

1)導緻引用計數+1的情況

對象被建立,例如a=23
對象被引用,例如b=a
對象被作為參數,傳入到一個函數中,例如func(a)
對象作為一個元素,存儲在容器中,例如list1=[a,a]
      

2)導緻引用計數-1的情況

對象的别名被顯式銷毀,例如del a
對象的别名被賦予新的對象,例如a=24
一個對象離開它的作用域,例如f函數執行完畢時,func函數中的局部變量(全局變量不會)
對象所在的容器被銷毀,或從容器中删除對象        

3)檢視一個對象的引用計數

import sys
a = "hello world"
sys.getrefcount(a)
      

可以檢視a對象的引用計數,但是比正常計數大1,因為調用函數的時候傳入a,這會讓a的引用計數+1

2.循環引用導緻記憶體洩露

引用計數的缺陷是循環引用的問題

import gc

class ClassA():
    def __init__(self):
        print('object born,id:%s'%str(hex(id(self))))

def f2():
    while True:
        c1 = ClassA()
        c2 = ClassA()
        c1.t = c2
        c2.t = c1
        del c1
        del c2

#把python的gc關閉
gc.disable()

f2()
      

執行f2(),程序占用的記憶體會不斷增大。  

建立了c1,c2後這兩塊記憶體的引用計數都是1,執行c1.t=c2和c2.t=c1後,這兩塊記憶體的引用計數變成2.
在del c1後,記憶體1的對象的引用計數變為1,由于不是為0,是以記憶體1的對象不會被銷毀,是以記憶體2的對象的引用數依然是2,在del c2後,同理,記憶體1的對象,記憶體2的對象的引用數都是1。
雖然它們兩個的對象都是可以被銷毀的,但是由于循環引用,導緻垃圾回收器都不會回收它們,是以就會導緻記憶體洩露。
      

3.垃圾回收

#coding=utf-8
import gc

class ClassA():
    def __init__(self):
        print('object born,id:%s'%str(hex(id(self))))
    # def __del__(self):
    #     print('object del,id:%s'%str(hex(id(self))))

def f3():
    print("-----0------")
    # print(gc.collect())
    c1 = ClassA()
    c2 = ClassA()
    c1.t = c2
    c2.t = c1
    print("-----1------")
    del c1
    del c2
    print("-----2------")
    print(gc.garbage)
    print("-----3------")
    print(gc.collect()) #顯式執行垃圾回收
    print("-----4------")
    print(gc.garbage)
    print("-----5------")

if __name__ == '__main__':
    gc.set_debug(gc.DEBUG_LEAK) #設定gc子產品的日志
    f3()
      

運作結果:

-----0------
object born,id:0x724b20
object born,id:0x724b48
-----1------
-----2------
[]
-----3------
gc: collectable <ClassA instance at 0x724b20>
gc: collectable <ClassA instance at 0x724b48>
gc: collectable <dict 0x723300>
gc: collectable <dict 0x71bf60>
4
-----4------
[<__main__.ClassA instance at 0x724b20>, <__main__.ClassA instance at 0x724b48>, {'t': <__main__.ClassA instance at 0x724b48>}, {'t': <__main__.ClassA instance at 0x724b20>}]
-----5------
      

說明:

垃圾回收後的對象會放在gc.garbage清單裡面
gc.collect()會傳回不可達的對象數目,4等于兩個對象以及它們對應的dict
      

有三種情況會觸發垃圾回收:

調用gc.collect(),
當gc子產品的計數器達到閥值的時候。
程式退出的時候
      

4.gc子產品常用功能解析

gc子產品提供一個接口給開發者設定垃圾回收的選項

。上面說到,采用引用計數的方法管理記憶體的一個缺陷是循環引用,而gc子產品的一個主要功能就是解決循環引用的問題。

1)常用函數:

gc.set_debug(flags) 設定gc的debug日志,一般設定為gc.DEBUG_LEAK

gc.collect([generation]) 顯式進行垃圾回收,可以輸入參數,0代表隻檢查第一代的對象,1代表檢查一,二代的對象,2代表檢查一,二,三代的對象,如果不傳參數,執行一個full collection,也就是等于傳2。 傳回不可達(unreachable objects)對象的數目

gc.get_threshold() 擷取的gc子產品中自動執行垃圾回收的頻率。

gc.set_threshold(threshold0[, threshold1[, threshold2]) 設定自動執行垃圾回收的頻率。

gc.get_count() 擷取目前自動執行垃圾回收的計數器,傳回一個長度為3的清單
      

2)gc子產品的自動垃圾回收機制

必須要import gc子產品,并且

is_enable()=True

才會啟動自動垃圾回收。

這個機制的

主要作用就是發現并處理不可達的垃圾對象

垃圾回收=垃圾檢查+垃圾回收

在Python中,采用分代收集的方法。把對象分為三代,一開始,對象在建立的時候,放在一代中,如果在一次一代的垃圾檢查中,改對象存活下來,就會被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會被放到三代中。

gc子產品裡面會有一個長度為3的清單的計數器,可以通過gc.get_count()擷取。

例如(488,3,0),其中488是指距離上一次一代垃圾檢查,Python配置設定記憶體的數目減去釋放記憶體的數目,注意是記憶體配置設定,而不是引用計數的增加。例如:

print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)
      

3是指距離上一次二代垃圾檢查,一代垃圾檢查的次數,同理,0是指距離上一次三代垃圾檢查,二代垃圾檢查的次數。

gc模快有一個自動垃圾回收的

閥值

,即通過gc.get_threshold函數擷取到的長度為3的元組,例如(700,10,10) 每一次計數器的增加,gc子產品就會檢查增加後的計數是否達到閥值的數目,如果是,就會執行對應的代數的垃圾檢查,然後重置計數器

例如,假設閥值是(700,10,10):

當計數器從(699,3,0)增加到(700,3,0),gc子產品就會執行gc.collect(0),即檢查一代對象的垃圾,并重置計數器為(0,4,0)
當計數器從(699,9,0)增加到(700,9,0),gc子產品就會執行gc.collect(1),即檢查一、二代對象的垃圾,并重置計數器為(0,0,1)
當計數器從(699,9,9)增加到(700,9,9),gc子產品就會執行gc.collect(2),即檢查一、二、三代對象的垃圾,并重置計數器為(0,0,0)
      

注意點

gc子產品唯一處理不了的是循環引用的類都有__del__方法,是以項目中要避免定義__del__方法

import gc

class ClassA():
    pass
    # def __del__(self):
    #     print('object born,id:%s'%str(hex(id(self))))

gc.set_debug(gc.DEBUG_LEAK)
a = ClassA()
b = ClassA()

a.next = b
b.prev = a

print "--1--"
print gc.collect()
print "--2--"
del a
print "--3--"
del b
print "--3-1--"
print gc.collect()
print "--4--"
      

運作結果:  

--1--
0
--2--
--3--
--3-1--
gc: collectable <ClassA instance at 0x21248c8>
gc: collectable <ClassA instance at 0x21248f0>
gc: collectable <dict 0x2123030>
gc: collectable <dict 0x2123150>
4
--4--
      

如果把del打開,運作結果為:  

--1--
0
--2--
--3--
--3-1--
gc: uncollectable <ClassA instance at 0x6269b8>
gc: uncollectable <ClassA instance at 0x6269e0>
gc: uncollectable <dict 0x61bed0>
gc: uncollectable <dict 0x6230c0>
4
--4--      

"一勞永逸" 的話,有是有的,而 "一勞永逸" 的事卻極少