python垃圾回收

Python內存管理機制

Python內存管理機制主要包括以下三個方面:

    引用計數機制
    垃圾回收機制
    內存池機制

引用計數

舉個例子說明引用是什麼:

a = 1

如上爲一個簡單的賦值語句,1就是對象,a就是引用,引用a指向對象1。
同理:

b = 1

b也是對象1的引用。
通過內置函數id()返回對象的地址。

print id(a)  #43220320
print id(b)  #43220320

當我們創建多個等於1的引用時,實際上是讓所有這些引用指向同一個對象。爲了檢驗兩個引用指向同一個對象,我們可以用is關鍵字。is用於判斷兩個引用所指向的對象是否相同。

print (a is b)  #True

在Python中,整數和短小的字符,Python都會緩存這些對象,以便重複使用。賦值語句,只是創造了新的引用,而不是對象本身。長的字符串和其它對象可以有多個相同的對象,可以使用賦值語句創建出新的對象。每個對象都有存有指向該對象的引用總數,即引用計數(reference count)。
可以使用sys.getrefcount()獲得引用計數,需要注意的是,當使用某個引用作爲參數,傳遞給getrefcount()時,參數實際上創建了一個臨時的引用。因此,getrefcount()所得到的結果,會比期望的多1。

from sys import getrefcount

a = [1, 2, 3]
print(getrefcount(a)) # 2

b = a
print(getrefcount(b)) # 3

    引用計數增加
    1.對象被創建:x=4
    2.另外的別人被創建:y=x
    3.被作爲參數傳遞給函數:foo(x)
    4.作爲容器對象的一個元素:a=[1, x, ‘33’]

    引用計數減少
    1.一個本地引用離開了它的作用域。比如上面的foo(x)函數結束時,x指向的對象引用減1。
    2.對象的別名被顯式的銷燬:del x ;或者del y
    3.對象的一個別名被賦值給其他對象:x=789
    4.對象從一個窗口對象中移除:myList.remove(x)
    5.窗口對象本身被銷燬:del myList,或者窗口對象本身離開了作用域。

垃圾回收

   引用計數
    引用計數也是一種垃圾收集機制,而且也是一種最直觀,最簡單的垃圾收集技術。當Python的某個對象的引用計數降爲0時,說明沒有任何引用指向該對象,該對象就成爲要被回收的垃圾了。比如某個新建對象,它被分配給某個引用,對象的引用計數變爲1。如果引用被刪除,對象的引用計數爲0,那麼該對象就可以被垃圾回收。
    不過如果出現循環引用的話,引用計數機制就不再起有效的作用了

a = []
b = []
a.append(b)
b.append(a)
print a  # [[[…]]]
print b  # [[[…]]]

循環引用可以使一組對象的引用計數不爲0,然而這些對象實際上並沒有被任何外部對象所引用,它們之間只是相互引用。這意味着不會再有人使用這組對象,應該回收這組對象所佔用的內存空間,然後由於相互引用的存在,每一個對象的引用計數都不爲0,因此這些對象所佔用的內存永遠不會被釋放。
Python又引入了其他的垃圾收集機制來彌補引用計數的缺陷:“標記-清除“,“分代回收”兩種收集技術。

    標記清除

#第一組循環引用#
a = [1,2]
b = [3,4]
a.append(b)
b.append(a)
del a

##

#第二組循環引用#

c = [4,5]
d = [5,6]
c.append(d)
d.append(c)
del c
del d
#至此,原a和原c和原d所引用的對象的引用計數都爲1,b所引用的對象的引用計數爲2,
e [7,8]
del e

 

現在說明一下標記清除:代碼運行到上面這塊了,此時,我們的本意是想清除掉c和d和e所引用的對象,而保留a和b所引用的對象。但是c和d所引用對象的引用計數都是非零,原來的簡單的方法只能清除掉e,c和d所引用對象目前還在內存中。

 

假設,此時我們預先設定的週期時間到了,此時該標記清除大顯身手了。他的任務就是,在a,b,c,d四個可變對象中,找出真正需要清理的c和d,而保留a和b。

 

首先,他先劃分出兩撥,一撥叫root object(存活組),一撥叫unreachable(死亡組)。然後,他把各個對象的引用計數複製出來,對這個副本進行引用環的摘除。摘除完畢,此時a的引用計數的副本是0,b的引用計數的副本是1,c和d的引用計數的副本都是0。那麼先把副本爲非0的放到存活組,副本爲0的打入死亡組。如果就這樣結束的話,就錯殺了a了,因爲b還要用,我們把a所引用的對象在內存中清除了b還能用嗎?顯然還得在審一遍,別把無辜的人也給殺了,於是他就在存活組裏,對每個對象都分析一遍,由於目前存活組只有b,那麼他只對b分析,因爲b要存活,所以b裏的元素也要存活,於是在b中就發現了原a所指向的對象,於是就把他從死亡組中解救出來。至此,進過了一審和二審,最終把所有的任然在死亡組中的對象通通殺掉,而root object繼續存活。b所指向的對象引用計數任然是2,原a所指向的對象的引用計數仍然是1

    分代回收
    從前面“標記-清除”這樣的垃圾收集機制來看,這種垃圾收集機制所帶來的額外操作實際上與系統中總的內存塊的數量是相關的,當需要回收的內存塊越多時,垃圾檢測帶來的額外操作就越多,而垃圾回收帶來的額外操作就越少;反之,當需回收的內存塊越少時,垃圾檢測就將比垃圾回收帶來更少的額外操作。
    舉個例子來說明:
    當某些內存塊M經過了3次垃圾收集的清洗之後還存活時,我們就將內存塊M劃到一個集合A中去,而新分配的內存都劃分到集合B中去。當垃圾收集開始工作時,大多數情況都只對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間後才進行,這就使得垃圾收集機制需要處理的內存少了,效率自然就提高了。在這個過程中,集合B中的某些內存塊由於存活時間長而會被轉移到集合A中,當然,集合A中實際上也存在一些垃圾,這些垃圾的回收會因爲這種分代的機制而被延遲。

內存池

python內存機制層次

    Python的內存機制呈現金字塔形狀,-1,-2層主要有操作系統進行操作;
    第0層是C中的malloc,free等內存分配和釋放函數進行操作;
    第1層和第2層是內存池,有Python的接口函數PyMem_Malloc函數實現,當對象小於256K時有該層直接分配內存;
    第3層是最上層,也就是我們對Python對象的直接操作;

Python在運行期間會大量地執行malloc和free的操作,頻繁地在用戶態和核心態之間進行切換,這將嚴重影響Python的執行效率。爲了加速Python的執行效率,Python引入了一個內存池機制,用於管理對小塊內存的申請和釋放。
Python內部默認的小塊內存與大塊內存的分界點定在256個字節,當申請的內存小於256字節時,PyObject_Malloc會在內存池中申請內存;當申請的內存大於256字節時,PyObject_Malloc的行爲將蛻化爲malloc的行爲。當然,通過修改Python源代碼,我們可以改變這個默認值,從而改變Python的默認內存管理行爲。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章