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