1. 程式人生 > 其它 >python垃圾回收和記憶體管理

python垃圾回收和記憶體管理

目錄
# 特別鳴謝:
	Alex的部落格:https://pythonav.com/wiki/detail/6/88/
    Alex的b站視訊:https://www.bilibili.com/video/BV1F54114761?p=5&spm_id_from=pageDriver

一句話概述

引用計數為主,標記清除、分代回收為輔

1 引用計數

1.1 refchain

# 在Python的C原始碼中有一個名為refchain的環狀雙向連結串列。Python程式中只要建立物件都會把這個物件新增到refchain這個連結串列中。

# 型別封裝結構
	refchain中所有物件內部都至少包含以下4個元素[上一個物件,下一個物件,ob_refcnt,型別]。然後根據不同的資料型別,有不同的值儲存方案(c原始碼中規定的),這裡不深入了。

# 例如:
    age = 18
	name = "武沛齊"

1.2 引用計數

# 在refchain中的所有物件內部都有一個ob_refcnt用來儲存當前物件的引用計數器,顧名思義就是自己被引用的次數。

# 當值被多次引用時候,不會在記憶體中重複建立資料,而是引用計數器+1 。 當物件被銷燬時候同時會讓引用計數器-1,如果引用計數器為0,則將物件從refchain連結串列中摘除,同時在記憶體中進行銷燬(暫不考慮快取等特殊情況)。

2 標記刪除

2.1 迴圈引用

# 引用計數的不足
	引用計數器進行垃圾回收非常方便和簡單,但他還是存在迴圈引用的問題,導致無法正常的回收一些資料。
    
# 舉例
	v1 = [11,22,33]       
	v2 = [44,55,66]        
	v1.append(v2)        
	v2.append(v1)        
	del v1    
	del v2    
    此時,v1和v2已經不用了,但他們的引用計數仍為1,而且永遠不會為0,導致迴圈引用問題。專案中如果這種程式碼太多,就會導致記憶體一直被消耗,直到記憶體被耗盡,程式崩潰。

2.2 標記刪除

# 為了解決迴圈引用的問題,引入了標記清除技術.

# 實現方案:
	在底層又維護了一個連結串列,專門放入一些可能出現迴圈引用的資料,然後定期掃描,如果真的存在迴圈引用,則將雙方的引用計數減一,再根據引用計數是否為0來決定是否回收。
    
# 存在的問題:
1.什麼時候掃描?
2.每次掃描耗時久,資源消耗大。

3 分代回收

# 將可能存在迴圈匯入的物件維護成3個連結串列,當達到閾值時,就會對相應的連結串列中的每個物件做一次掃描。將迴圈引用各自減1並且銷燬引用計數器為0的物件,最後將引用計數不為0的物件放入下一個連結串列。
	0代:0代中物件達到700個掃描一次
    1代:0代掃描10次,1代掃描一次
    2代:1代掃描10次,2代掃描一次

4 快取機制(記憶體管理優化)

4.1 池(int)

# 為了避免重複的建立和銷燬,維護了一個池。裡面放入了常用的整型資料物件,小資料池範圍:-5~257。當我們使用這些整數時,不會重新開闢記憶體,而是直接引用。

# 當我們不再使用該資料時,資料仍然在池中,應用計數為1,不會被清除。

# 以上說明以整型為例,其它資料型別也有對應的池。

4.2 free_list

# 引用計數器為0時,不會真正銷燬物件,而是將他放到一個名為 free_list 的連結串列中,之後會再建立物件時不會在重新開闢記憶體,而是在free_list中將之前的物件來並重置內部的值來使用

# 不同的資料型別有不同的free_list連結串列,這裡不深入。