Python垃圾回收詳解:引用計數+標記清理+分代回收


Python采用的是引用計數機制為主,標記-清理和分代收集兩種機制為輔的策略。

1、引用計數

python中一切皆對象,所以python底層計數結構地就可以抽象為:

引用計數結構體{
引用計數;
引用的對象
}

是不是簡單明了。現在我們先去考慮一下,什么情況下引用計數+1,什么情況下-1,當引用次數為0時,肯定就是需要進行回收的時刻。

  • 引用計數+1的情況
1、對象被創建時,例如 mark="帥哥" 2、對象被copy引用時,例如 mark2=mark,此時mark引用計數+1 3、對象被作為參數,傳入到一個函數中時 4、對象作為一個子元素,存儲到容器中時,例如 list=[mark,mark2]
  • 引用計數-1的情況
 
1、對象別名被顯式銷毀,例如 del mark 2、對象引用被賦予新的對象,例如mark2=mark3,此時mark引用計數-1(對照引用計數+1的情況下的第二點來看) 3、一個函數離開他的作用域,例如函數執行完成,它的引用參數的引用計數-1 4、對象所在容器被銷毀,或者從容器中刪除。

  • 查看引用計數

實例:

import sys
a = "mark 帥哥" print(sys.getrefcount(a))
  • 引用計數機制優點
1、簡單、直觀
2、實時性,只要沒有了引用就釋放資源。
  • 引用計數機制缺點
 
1、維護引用計數需要消耗一定的資源
2、循環應用時,無法回收。也正是因為這個原因,才需要通過標記-清理和分代收集機制來輔助引用計數機制。

2、標記-清理

由上面內容我們可以知道,引用計數機制有兩個缺點,缺點1還可以勉強讓人接受,缺點2如果不解決,肯定會引起內存泄露,為了解決這個問題,引入了標記刪除。

我們先來看個實例,從實例中領會標記刪除:

a=[1,2]#假設此時a的引用為1 b=[3,4]#假設此時b的引用為1 #循環引用 a.append(b)#b的引用+1=2 b.append(a)//a的引用+1=2 假如現在需要刪除a,應該如何回收呢?(注意刪除a可以使用del a,這樣a這個引用就不存在了,但是它指向的對象,在標記刪除后還存在,因為還被b使用者) c=[5,6]#假設此時c的引用為1 d=[7,8]#假設此時d的引用為1 #循環引用 c.append(d)#c的引用+1=2 d.append(c)#d的引用+1=2 假如現在需要同時刪除c、d,應該如何回收呢?

首先我們應該已經知道,不管上面兩種情況的哪一個都無法只通過計數來完成回收,因為隨便刪除一個變量,它的引用只會-1,變成1,還是大於0,不會回收,為了解決這個問題,開始看標記刪除來大展神威吧。

puthon標記刪除時通過l兩個容器來完成的:死亡容器、存活容器。

首先,我們先來分析情況2,刪除c、d 刪除后,c的引用為1,d的引用為1,根據引用計數,還無法刪除 標記刪除第一步:對執行刪除操作后的每個引用-1,此時c的引用為0,d的引用為0,把他們都放到死亡容器內。把那些引用仍然大於0的放到存活容器內。 標記刪除第二步:遍歷存活容器,查看是否有的存活容器引用了死亡容器內的對象,如果有就把該對象(注意是對象,比如0x7f94bb602f80,不是對象的引用)從死亡容器內取出,放到存活容器內。 由於c、d都沒有對象引用他們了,所以經過這一步驟,他們還是在死亡組。 標記刪除第三部:將死亡組所有對象刪除。 這樣就完成了對從c、d的刪除。

同樣道理,我們來分析:只刪除a的過程:

標記刪除第一步:對執行刪除(-1)后的每個引用-1,那么a的引用就是0,b的引用為1,將a放到死亡容器,將b放到存活容器。
標記刪除第二步:循環存活容器,發現b引用a,復活a:將a放到存活容器內。
標記刪除第三步:刪除死亡容器內的所有對象。

綜上所說,發現對於循環引用,必須將循環引用的雙發對象都刪除,才可以被回收。

標記-清理就是這么簡單,😀。

3、分代收集

經過上面的【標記-清理】方法,已經可以保證對垃圾的回收了,但還有一個問題,【標記-清理】什么時候執行比較好呢,是對所有對象都同時執行嗎?

同時執行很顯然不合理,我們知道,存活越久的對象,說明他的引用更持久(好像是個屁話,引用不持久就被刪除了),為了更合理的進行【標記-刪除】,就需要對對象進行分代處理,思路很簡單:

1、新創建的對象做為0代
2、每執行一個【標記-刪除】,存活的對象代數就+1
3、代數越高的對象(存活越持久的對象),進行【標記-刪除】的時間間隔就越長。這個間隔,江湖人稱閥值。

是不是很簡單呢。

4、三種情況觸發垃圾回收

1、調用gc.collect() 2、GC達到閥值時 3、程序退出時

5、小整數對象池與intern機制:這個機理是有的,但是下文的數據有待認證

由於整數使用廣泛,為了避免為整數頻繁銷毀、申請內存空間,引入了小整數對象池。[-5,257)是提前定義好的,不會銷毀,單個字母也是。

那對於其他整數,或者其他字符串的不可變類型,如果存在重復的多個,例如:

a1="mark" a2="mark" a3="mark" a4="mark" .... a1000="mark"

如果每次聲明都開辟出一段空間,很顯然不合理,這個時候python就會使用intern機制,靠引用計數來維護。

總計:

 
1、小整數[-5,257):共用對象,常駐內存
2、單個字符:共用對象,常駐內存
3、單個單詞等不可變類型,默認開啟intern機制,共用對象,引用計數為0時銷毀。

來源:https://segmentfault.com/a/1190000016078708


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM