結構體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次。