python垃圾回收機制 概況

 突然被人問到,了不瞭解python的gc(垃圾回收)機制,當時還真是一頭霧水,今天參照着《python源碼剖析》細細研究了一下,一下是鄙人的總結:(至少適用:python2.5-2.7)

  python採取基於引用計數的垃圾回收機制,此機制也是當前最簡單、最直觀的垃圾回收技術,只要某個對象的引用計數爲零,則就消除該對象,回收內存。然而,這種機制存在一種致命的弱點,不能處理循環引用的情況。如,python中的兩個list對象,a,b,a引用b,b引用a,除了相互引用之外,沒有其他外部變量引用a或者b,則可以認爲這兩個變量是垃圾(可以回收的),但其引用計數卻不爲零,所以最基本的引用計數gc機制不能處理這種情況,而python的gc機制大部分的工作都是用來處理這種循環引用的情況的。
爲了解決這個致命弱點,python引入了其他的垃圾收集技術。一般的垃圾收集包括兩個過程:垃圾檢測和垃圾回收,而在Python中垃圾檢測採用了標記-清除技術,回收階段則使用了分代回收技術。接下來先簡要說明這兩種技術。
  標記-清除技術:這種技術也遵循垃圾收集機制的通用特徵:先檢測,再清除。這種技術的實施過程簡要如下:1)尋找根對象集合 ,利用有效利用計數,可以打破循環引用計數 2)從根對象集合出發,尋找這些對象的每一個引用如A,則A爲可達的(reachable),可達對象不可刪除;剩下的對象爲不可達對象(unreachable),這些對象可刪除,這就是垃圾檢測階段,此處採用了三色標記處理,黑色代表根節點,灰色代表中層節點,白色代表葉子節點也就是可刪除的節點。 3)垃圾檢測結束後,最終形成一張有向無環圖。保留非葉子節點,回收葉子節點。

  分代回收技術:某個對象經過垃圾收集處理的次數越多,且仍存活,則這個對象越不像是空閒對象,即活得越長的對象越不應該被收集。python中將所有內存塊分成了三代,其實就是添加了額外表頭信息的三個鏈表,其數據結構爲:

struct gc_generation{
    PyGC_Head head;
    int threshold;/*某一代所允許的收集的最大對象數目*/
    int count;/*此鏈中可收集的對象數目*/
};

所有新創建的對象都會被添加到_PyGC_Generation0(即第0代鏈表中,因爲新創建的對象最可能是不可達對象)。若某個鏈表中所受即的對象數目超過了其對應的threshold時,具體的闕值可以去obgcmodule.c中去查看,則激活對於此代(記爲n)鏈表和比n小的代鏈表中對象的垃圾收集處理工作,即先檢測再回收。

  分代鏈表中存的是什麼?存的是python創建的可監控容器(container)對象。像int,string這些類型的對象不可能包含對其他對象的引用(object對象)除外,所以不會存在循環引用的問題,而如列表,元組,字典,集合這些內建對象和自定義的類的實例則可能存在循環引用問題,所以python處理循環引用問題時,主要是處理這些對象,可以大大加快處理效率。可監控容器對象包括一個PyGC_head首部,和contianer對象自身的數據,包括一個PyObject首部和數據信息。其中

typedef union _gc_head{
    strutc{
    union _gc_head *next;
    union _gc_head *prev;
    int gc_refs;
    }gc;
    long double dummy;

}PyGC_head

正是有了這樣的頭部,python源碼中利用_PyObject_GC_TRACK()函數將某個可監控對象加入到某個代鏈表中,利用_PyObject_GC_UNTRACK()方法將某個可監控對象從其所屬代鏈表中剔除。

  對於某一代鏈表,還要進行前面提到的標記操作,最後纔是清除操作。
 
  python的GC流程大致如下:分代->標記->清除

  對於自建類中存在__del__方法的情況,python創建了finalizers列表,專門收集這些類的實例對象,清除不掉。這種情況,只能在自己寫代碼時,不要在自定義類中添加__del__方法了,以免造成內存泄露問題。

參考:陳儒,《python源碼分析》


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章