python【內存管理機制】和【垃圾回收機制】

結構體PyObject和PyVarObject

在瞭解內存管理機制前先要知道倆個結構體

結構體1:

#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;

源碼中用c語言編寫的一個結構體PyObject,內部包含四個值分別是:鏈表中上一個值的地址、下一個值的地址、引用計數器、類型。

結構體2:

typedef struct {
    PyObject ob_base;   // 內部封裝4個值
    Py_ssize_t ob_size; // 成員個數
} PyVarObject;

結構體PyVarObject中除了包含結構體1中的四個值外額外多了一個成員個數

float類型

#define PyObject_HEAD        PyObject ob_base;
typedef struct {
    PyObject_HEAD // 上一個、下一個、引用計數器、類型
    double ob_fval; // 1.1
} PyFloatObject;

在創建一個floatv1 = 1.1類型時會調用PyFloatObject保存四個值:鏈表中上一個值的地址、下一個值的地址、引用計數器爲1,類型爲float,除了這四個值外還會保存一個ob_fval,它其實就是1.1這個值

list類型

#define PyObject_VAR_HEAD      PyVarObject ob_base;

typedef struct {
    PyObject_VAR_HEAD  //5個值,上一個、下一個、引用計數器、類型、成員個數
    PyObject **ob_item; // 列表中的每個元素內存地址
} PyListObject;

在創建list類型時,調用pyvarobject,在內部保存5個值,處此之外ob_item還保存了列表中每個元素的內存地址,這也是爲什麼列表中存的不是值本身而是內存地址的原因。

在Python中由單個元素組成的對象,他的內部是包含PyObject結構體創建對象。 由多個元素組成的對象,他的內部是包含PyVarObject結構體創建的對象。

單個元素組成的對象有哪些?在python中其實只有float是由單個元素組成,其他類型都是由多個元素組成

內存管理機制

創建變量:

# 第一步:開闢內存,並做初始化:上一個、下一個、引用計數器=1、類型=str、成員個數=3
# 第二步:將對象加入到雙向鏈表中
v1 = "abc"

# 第一步:開闢內存,並做初始化:上一個、下一個、引用計數器=1、類型=list、成員個數=3
# 第二步:將對象加入到雙向鏈表中
v2 = [11,22,33]

# 第一步:開闢內存,並做初始化:上一個、下一個、引用計數器=1、類型=float
# 第二步:將對象加入到雙向鏈表中
v3 = 9.9

# v1指向的內存中 引用計數器+1
v4 = v1

# v1指向的內存中 引用計數器+1
temp = []
temp.append(v1)

# v4指向的那個內存中 引用計數器-1
del v4

# v4指向的那個內存中 引用計數器-1
temp.remove(v1)

# v4指向的那個內存中 引用計數器先 +1 再 -1
def func(arg):
    pass
func(v1)

當引用計數器爲0時,則將內存銷燬並在雙向鏈表中移除。

python針對float和list類型做了優化處理:當list或float類型銷燬時其實它不會真正的銷燬,只會在雙向鏈表中移除,並存放到另一個鏈表free_list中,當在創建此類型對象時不會再重新開闢內存地址,而是去free_list中查找,如果存在就使用。

Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> v1 = 1.1
>>> print(id(v1))
1361327366504
>>> del v1
>>> v2 = 1.1
>>> print(id(v2))
1361327366504
>>>
>>>> l1 = [1,2,3]
>>> print(id(l1))
1361359174472
>>> del l1
>>> l2 = [1,2,3]
>>> print(id(l2))
1361359174472
>>>

這個列表也不是無限的,在源碼中可以看到list類型上限是80個,float類型上限是100個

#define PyList_MAXFREELIST 80
#define PyFloat_MAXFREELIST    100

垃圾回收機制

python的垃圾回收機制可以用一句話概括以引用計數器爲主、標記清除和分代回收爲輔。 引用計數器就是上面pyobject和中的ob_refcnt,但是僅靠引用計數器會導致一個bug無法解決:當兩個元素相互引用時,引用計數器永遠不會變爲0。所以產生了標記清除這個概念。

標記清除:
python在做內存管理時其實一共維護了兩個鏈表,第一個鏈表存儲不會出現相互引用的數據類型,比如str、int、bool等。而另一個鏈表用來存儲可能會出現相互引用的數據類新,比如list、dict、set、tuple、對象。針對此鏈表Python內部會定期進行檢查,如果存在循環引用(檢查鏈表時發現引用了之前已經檢查過的元素),則讓雙方引用計數器均-1,當計數器爲0時,則認爲是垃圾,就進行清除和銷燬。

# 相互引用示例
l1 = [1,2,3]
l2 = [4,5,6]
l1.append(l2)
l2.append(l1)
del l1
del l2

分代回收:

分代回收其實是python做的一種優化機制,爲減少掃描元素個數和次數,將常駐內存中的元素可以升級,python內部總共維護了3個鏈表,稱爲:0代、1代、2代,當多次掃描0代鏈表時發現某個元素都不是垃圾的時候,就會將這個元素放到1代鏈表中,當多次掃描1代鏈表發現某個元素常駐時,則將這個元素放到2代鏈表中。默認情況下0代鏈表掃描10次1代鏈表掃描1次,1代鏈表掃描10次2代鏈表掃描1次

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