Python垃圾回收機制
引用計數器為主
標記清除和分代回收為輔
+ 快取機制
1. 引用計數器
1.1 環狀雙向連結串列 refchain
在python程式中建立的任何物件都會放在refchain連結串列中。
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
// 宏定義,包含 上一個、下一個,用於構造雙向連結串列用。(放到refchain連結串列中時要用到)
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
name = "阿瑋"
age = 18
hobby = ["健身", "美女"]
內部會建立一些資料 [ 上一個物件、下一個物件、型別、引用個數 ]
name = "阿瑋"
new = name # 引用個數變成2
內部會建立一些資料 [ 上一個物件、下一個物件、型別、引用個數、val=18 ]
age = 18
內部會建立一些資料 [ 上一個物件、下一個物件、型別、引用個數、items=元素、元素個數 ]
hobby = ["健身", "美女"]
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
// 宏定義,包含 上一個、下一個,用於構造雙向連結串列用。(放到refchain連結串列中時要用到)
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
typedef struct _object {
_PyObject_HEAD_EXTRA; // 用於構造雙向連結串列
Py_ssize_t ob_refcnt; // 引用計數器
struct _typeobject *ob_type; // 資料型別
} PyObject;
typedef struct {
PyObject ob_base; // PyObject物件
Py_ssize_t ob_size; // Number of items in variable part,即:元素個數
} PyVarObject;
在C原始碼中如何體現每個物件中都有的相同的值:PyObject結構體(4個值)。
有多個元素組成的物件:PyObject結構體(4個值)+ ob_size = PyVarObject。
1.2 型別封裝結構體
float型別
typedef struct {
PyObject_HEAD;
double ob_fval;
};
data = 3.14;
內部會建立:
_ob_next = refchain中的下一個物件
_ob_prev = refchain中的上一個物件
ob_refcnt = 1
ob_type = float
ob_fval = 3.14
int型別
struct _longobect {
PyObject_VAR_HEAD;
digit ob_dit[1];
};
/* Long (arbitrary precision) integer object interface */
typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */
list型別
typedef struct {
PyObject_VAR_HEAD;
PyObject ** ob_item;
Py_ssize_t allocated;
} PyListObject;
tuple型別
typedef struct {
PyObject_VAR_HEAD;
PyObject *ob_item[1];
} PyTupleObject;
dict型別
typedef struct {
PyObject_HEAD;
Py_ssize_t ma_used;
PyDictKeyObject *ma_keys;
PyObject **ma_values;
} PyDictObject;
1.3 引用計數器
v1 = 3.14
v2 = 999
v3 = (1,2,3)
當python程式執行時,會根據資料型別的不同找到其結構體,根據結構體中的欄位來進行建立相關的資料,然後將物件新增到refchain雙向連結串列中。
在C原始碼中有兩個關鍵的結構體:PyObject、PyVarObject。
每個物件中有 ob_refcnt 就是引用計數器,值預設為1,當有其他變數引用這個物件時,引用計數器就會發生變化。
引用
a = 99999
b = a
# 此時 99999 這個物件引用計數器的值為2
'''
下面情況會導致引用計數器+1:
1.物件被建立,如 a = 2
2.物件被引用,如 b = a
3.物件被作為引數,傳入到一個函式中
4.物件作為一個元素,儲存在容器中
可以透過sys包中的getrefcount()來獲取一個名稱所引用的物件當前的引用計數器的值(注意這裡getrefcount()本身會使得引用計數器+1)
'''
刪除引用
a = 99999
b = a
# b變數刪除,b對應物件的引用計數器-1
def b
# a變數刪除,a對應物件的引用計數器-1
'''
下面情況會導致引用計數器-1:
1.變數被顯示銷燬 del
2.變數被賦予新的物件
3.一個物件離開它的作用域
4.物件所在的容器被銷燬或從容器中刪除物件
'''
# 當一個物件的引用計數器為0時,意味著沒有人再使用這個物件了,這個物件就是垃圾,垃圾回收。
# 回收:1.物件從rechain連結串列移出。2.將物件銷燬,記憶體歸還。
1.4 迴圈引用問題
由於 v1 指向的物件引用了 v2,v2 指向的物件也引用了 v1,當將 v1、v2 兩個變數刪除時,雖然引用計數器會減1,但是兩個物件間還存在迴圈引用,而此時已經沒有變數能去指向它們,這兩個物件就會在記憶體中常駐無法處理。
2. 標記清除
目的:為了解決引用計數器迴圈引用的問題。
實現:在python的底層再維護一個連結串列,連結串列中專門放哪些可能存在迴圈應用的物件(容器類物件:list、tuple、dict、set)。
在Python內部某種情況下觸發,會去掃描可能存在迴圈引用的連結串列中的每個元素,檢查是否有迴圈引用,如果有則讓雙方的引用計數器-1;如果是0則垃圾回收。
2.1 標記階段
遍歷所有物件,如果是可達的(reachable),也就是還有物件引用它,那麼就將該物件標記為可達
該階段從某個物件開始掃描(而不是從變數),如果變數A引用了變數B,則將變數B的引用計數器-1(指的是gc_ref),然後掃描變數B 鄭州人流手術醫院
如圖所示,link1、link2、link3形成了一個引用環,link4自引用。從link1開始掃描,link1引用了link2,則link2的gc_ref-1,接著掃描link2…
像這也將連結串列中所有物件考察一遍後,兩個連結串列中的物件ref_count和gc_ref圖如所示,這一步操作就相當於解除了迴圈引用對引用計數器的影響
如果gc_ref為0,則將物件標記為 GC_TENTATIVELY_UNREACHABLE,並且被移至”Unreachable“連結串列中,如下圖link3、link4(我覺得link2應該也是)
如果gc_ref不為0,那麼這個物件會被標記為可達的GC_REACHABLE,同時當gc發現有一個節點是可達的,那麼它會遞迴式的從該節點觸發將所有可達的節點標記為GC_REACHABLE,這樣把link2、link3救回來
2.2 清除階段
將被標記成 GC_UNREACHABLE 的物件銷燬,記憶體歸還(也就是Unreachable連結串列中的物件)
2.3 標記清除的問題
在標記清除演算法開始後,會暫停整個應用程式,等待標記清除結束後才會恢復應用的執行,且對迴圈引用的掃描代價大,每次掃描耗時可能很久
3. 分代回收
將可能存在迴圈引用的物件維護成3個連結串列:
0代:0代中物件個數達到700個掃描一次
1代:0代掃描10次,則1代掃描一次
2代:1代掃描10次,則2代掃描一次
4. 小結
在python中維護了一個refchain的雙向環狀連結串列,這個連結串列中儲存程式建立的所有物件,每種型別的物件都有一個ob_refcnt引用計數器的值,當引用計數器變為0時會進行垃圾回收(物件銷燬、refchain中移出)。
但是,在python中對於那些可以有多個元素組成的物件可能會存在迴圈引用的問題,為了解決這個問題,python又引入了標記清除和分代回收,在其內部維護了4個連結串列,分別為:
refchain
2代
1代
0代
在原始碼內部,當達到各自的閾值時,就會觸發掃描連結串列進行標記清除的動作(有迴圈引用則各自-1)。
But,原始碼內部在上述流程中提出了最佳化機制。
5. Python快取機制
5.1 程式碼塊&小資料池
Python 程式碼塊、快取機制
5.2 free_list
快取機制
一文搞定Python垃圾回收機制
垃圾回收機制剖析
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2693574/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- JAVA垃圾回收機制和Python垃圾回收對比與分析JavaPython
- python進階(7)垃圾回收機制Python
- Java 垃圾回收機制Java
- JVM垃圾回收機制JVM
- java垃圾回收機制Java
- js垃圾回收機制JS
- JVM 垃圾回收機制JVM
- javascript 垃圾回收機制JavaScript
- 簡單介紹python的垃圾回收機制Python
- JS的垃圾回收機制JS
- jvm的垃圾回收機制JVM
- 剖析垃圾回收機制(上)
- java垃圾回收機制整理Java
- JavaScript的垃圾回收機制JavaScript
- PHP的垃圾回收機制PHP
- PHP的垃圾回收機制-回收週期PHP
- Python3 原始碼閱讀 - 垃圾回收機制Python原始碼
- 談談 JVM 垃圾回收機制JVM
- 【翻譯】PHP 垃圾回收機制PHP
- 圖解Golang垃圾回收機制!圖解Golang
- JS垃圾回收機制筆記JS筆記
- [效能][JVM]jvm垃圾回收機制JVM
- V8垃圾回收機制
- Flutter中的垃圾回收機制Flutter
- JVM垃圾回收機制入門JVM
- python垃圾回收機制(十分重要)Python
- 秒懂JVM的垃圾回收機制JVM
- Java的垃圾回收(Garbage Collection)機制Java
- C#垃圾回收機制詳解C#
- 聊聊JVM的垃圾回收機制GCJVMGC
- Python垃圾回收機制是什麼?有哪些優缺點?Python
- JDK 18 GC垃圾回收機制比較JDKGC
- javascript的垃圾回收機制指的是什麼?JavaScript
- 深入理解Go-垃圾回收機制Go
- 超詳細的node垃圾回收機制
- javascript的垃圾回收機制指的是什麼JavaScript
- js--閉包與垃圾回收機制JS
- PHP的垃圾回收機制-引用計數PHP