python垃圾回收和記憶體管理
阿新 • • 發佈:2021-10-28
目錄
# 特別鳴謝:
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連結串列,這裡不深入。