1. 程式人生 > 程式設計 >python中的垃圾回收(GC)機制

python中的垃圾回收(GC)機制

一、引用計數

Python 垃圾回收以引用計數為主,分代回收為輔。引用計數法的原理是每個物件維護一個ob_refcnt,用來記錄物件被引用的次數,也就是用來追蹤有多少個引用指向了物件,當發生以下四種情況的時候,物件的引用計數+1:

  • 物件被建立,比如:a = 14
  • 物件被引用,比如: b = a
  • 物件被作為引數,傳給函式,比如:func(a)
  • 物件作為容器中的一個元素,比如:List = {a,”a”,”b”,2}

與上述情況相對應,當發生以下四種情況時,物件的引用計數-1:

物件的別名被顯式銷燬,比如:del a
物件的別名被賦予新的物件,比如:a = 26
物件離開它的作用域,比如 func() 執行完畢時,函式裡面的所有區域性變數的引用計數都會減 1

將元素從容器中刪除,或者容器被銷燬
當物件的引用計數為 0 時,它將被 Python 虛擬機器回收。

在 Python 中一切皆物件,它們的核心是 Py_Object 結構體,所有 Python 物件的頭部都包含該結構:

// object.h
#define PyObject_HEAD   
 _PyObject_HEAD_EXTRA  
 Py_ssize_t ob_refcnt;  
 struct _typeobject *ob_type;
​
typedef struct _object {
 PyObject_HEAD
} PyObject;

比如 int 型別的定義如下:

// intobj.h
typedef struct {
 PyObject_HEAD
 long ob_ival;
} PyIntObject;

簡而言之,PyObject 是每個物件必有的內容,其中 ob_refcnt 是物件的引用計數。物件有新的引用時,它的 ob_refcnt 會增加;當物件的引用被刪除時,ob_refcnt 會減少。當引用計數為 0 時,物件的生命週期就結束了。

// object.h
#define Py_INCREF(op) (    
 _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA 
 ((PyObject*)(op))->ob_refcnt++)
​
#define Py_DECREF(op)     
 do {      
 if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA 
 --((PyObject*)(op))->ob_refcnt != 0)  
  _Py_CHECK_REFCNT(op)   
 else      
 _Py_Dealloc((PyObject *)(op));   
 } while (0)

引用計數有很明顯的優點:

  • 高效
  • 執行期沒有停頓,即實時性:物件一旦沒有引用,將直接被釋放。實時性還帶來一個好處是:處理回收記憶體的時間分攤到了平時
  • 物件有確定的生命週期
  • 易於實現

原始的引用計數法也有明顯的缺點:

  • 維護引用計數消耗資源,維護引用計數的次數和引用賦值成正比
  • 無法解決迴圈引用的問題

比如:

list1 = []
list2 = []
list1.append(list2)
list2.append(list1)

為了解決這兩個致命弱點,Python 又引入了以下兩種 GC 機制。

二、標記-清除

『標記-清除(Mark-Sweep)』演算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收演算法。它分為兩個階段:第一階段是標記階段,GC 會給所有『活動物件』打上標記;第二階段是回收沒有標記的『非活動物件』。那麼 GC 如何判斷哪些是活動物件、哪些是非活動物件呢?

物件之間通過引用(指標)連在一起,構成一個有向圖。物件是有向圖的頂點,引用關係是有向圖的弧。從根物件(root object)出發,遍歷有向圖,將可達的(reachable)物件標記為活動物件,不可達的物件就是要被清除的非活動物件。根物件是全域性變數、呼叫棧、暫存器。

python中的垃圾回收(GC)機制

在上圖中,把小黑圈視為全域性變數,也就是把它作為 root object,從小黑圈出發,物件 1 可直達,那麼它將被標記,物件 2、3 可間接到達,也會被標記,而 4 和 5 不可達,因此 1、2、3 是活動物件,4 和 5 是非活動物件,會被 GC 回收。

標記清除演算法作為 Python 的輔助垃圾回收技術,主要用於處理容器物件,比如 list、dict、tuple、instance 等,因為字串、數值等原子型別的物件不可能造成迴圈引用問題。Python 使用雙向連結串列將容器物件組織起來。不過這種簡單粗暴的標記清除演算法也有明顯的缺點:清除非活動物件前,必須順序掃描整個堆記憶體,哪怕只剩下小部分非活動物件,也要掃描所有物件。

三,分代回收

分代回收是一種以空間換時間的操作方式,Python 將記憶體根據物件的存活時間劃分為不同的集合,每個集合稱為一個代,Python 將記憶體分為了 3 代,分別為年輕代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),它們對應是 3 個連結串列,垃圾回收頻率隨著物件存活時間的增大而減小。新建立的物件都會被分配到年輕代,當年輕代連結串列的節點總數達到上限時,Python 垃圾收集機制就會被觸發,把可以被回收的物件回收掉,而不能被回收的物件會被移到中年代去,依此類推,老年代中的物件是存活時間最久的物件,甚至存活於整個系統的生命週期內。分代回收建立在標記清除的基礎之上,分代回收同樣作為 Python 處理容器物件的輔助垃圾回收技術。

以上就是python中的GC機制的詳細內容,更多關於python GC的資料請關注我們其它相關文章!