Python垃圾回收詳解

上一篇文章: 私有化規則與屬性Property
下一篇文章: Python進程專題總覽篇

高級語言通常都有垃圾回收機制,其中c、c++使用的是用戶本身管維護內存的方式,這種方式比較自由,但若是回收不當也會引發垃內存泄露等問題。而python採用的是引用計數機制爲主,標記-清理和分代收集兩種機制爲輔的策略。python

一、引用計數

python中一切皆對象,因此python底層計數結構地就能夠抽象爲:c++

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

是否是簡單明瞭。如今咱們先去考慮一下,什麼狀況下引用計數+1,什麼狀況下-1,當引用次數爲0時,確定就是須要進行回收的時刻。segmentfault

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

實例:app

import sys
a = "mark 帥哥"
print(sys.getrefcount(a))

結果:編輯器

4

備註:若是實際結果與上面不符,跟使用的編輯器有很大關係,重點是理解計數引用原理,不要太在乎爲啥不是1.函數

想理解緣由能夠轉看:https://stackoverflow.com/questions/45021901/why-does-a-newly-created-variable-in-python-have-a-ref-count-of-fourcode

  • 引用計數機制優勢
一、簡單、直觀
二、實時性,只要沒有了引用就釋放資源。
  • 引用計數機制缺點
一、維護引用計數須要消耗必定的資源
二、循環應用時,沒法回收。也正是由於這個緣由,才須要經過標記-清理和分代收集機制來輔助引用計數機制。

二、標記-清理

由上面內容咱們能夠知道,引用計數機制有兩個缺點,缺點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,應該如何回收呢?


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的放到存活容器內。

標記刪除第二步:遍歷存活容器,查看是否有的存活容器引用了死亡容器內的對象,若是有就把該對象從死亡容器內取出,放到存活容器內。
因爲c、d都沒有對象引用他們了,因此通過這一步驟,他們仍是在死亡組。

標記刪除第三部:將死亡組全部對象刪除。
這樣就完成了對從c、d的刪除。

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

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

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

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

三、分代收集

通過上面的【標記-清理】方法,已經能夠保證對垃圾的回收了,但還有一個問題,【標記-清理】何時執行比較好呢,是對全部對象都同時執行嗎?

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

一、新建立的對象作爲0代
二、沒執行一個【標記-刪除】,存活的對象代數就+1
三、代數越高的對象(存活越持久的對象),進行【標記-刪除】的時間間隔就越長。這個間隔,江湖人稱閥值。

是否是很簡單呢。

四、三種狀況觸發垃圾回收

一、調用gc.collect()
二、GC達到閥值時
三、程序退出時

五、小整數對象池與intern機制

因爲整數使用普遍,爲了不爲整數頻繁銷燬、申請內存空間,引入了小整數對象池。[-5,257)是提早定義好的,不會銷燬,單個字母也是。

那對於其餘整數,或者其餘字符串的不可變類型,若是存在重複的多個,例如:

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

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

總計:

一、小整數[-5,257):共用對象,常駐內存
二、單個字符:共用對象,常駐內存
三、單個單詞等不可變類型,默認開啓intern機制,共用對象,引用計數爲0時銷燬。

備註:利用gc模塊python也能夠實現GC控制,,有興趣能夠研究一下。

相關文章
相關標籤/搜索