上一篇文章: 私有化規則與屬性Property
下一篇文章: Python進程專題總覽篇
高級語言通常都有垃圾回收機制,其中c、c++使用的是用戶本身管維護內存的方式,這種方式比較自由,但若是回收不當也會引發垃內存泄露等問題。而python採用的是引用計數機制爲主,標記-清理和分代收集兩種機制爲輔的策略。python
python中一切皆對象,因此python底層計數結構地就能夠抽象爲:c++
引用計數結構體{ 引用計數; 引用的對象 }
是否是簡單明瞭。如今咱們先去考慮一下,什麼狀況下引用計數+1,什麼狀況下-1,當引用次數爲0時,確定就是須要進行回收的時刻。segmentfault
一、對象被建立時,例如 mark="帥哥" 二、對象被copy引用時,例如 mark2=mark,此時mark引用計數+1 三、對象被做爲參數,傳入到一個函數中時 四、對象做爲一個子元素,存儲到容器中時,例如 list=[mark,mark2]
一、對象別名被顯示銷燬,例如 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達到閥值時 三、程序退出時
因爲整數使用普遍,爲了不爲整數頻繁銷燬、申請內存空間,引入了小整數對象池。[-5,257)是提早定義好的,不會銷燬,單個字母也是。
那對於其餘整數,或者其餘字符串的不可變類型,若是存在重複的多個,例如:
a1="mark" a2="mark" a3="mark" a4="mark" .... a1000="mark"
若是每次聲明都開闢出一段空間,很顯然不合理,這個時候python就會使用intern機制,靠引用計數來維護。
總計:
一、小整數[-5,257):共用對象,常駐內存 二、單個字符:共用對象,常駐內存 三、單個單詞等不可變類型,默認開啓intern機制,共用對象,引用計數爲0時銷燬。