Python內(nèi)存管理機制主要包括以下三個方面:
引用計數(shù)機制
垃圾回收機制
內(nèi)存池機制
引用計數(shù)
舉個例子說明引用是什么:
a = 1
如上為一個簡單的賦值語句涮总,1就是對象织咧,a就是引用硬霍,引用a指向?qū)ο?宇葱。?
同理:
b = 1
b也是對象1的引用勺良。?
通過內(nèi)置函數(shù)id()返回對象的地址。
print id(a)? #43220320
print id(b)? #43220320
當我們創(chuàng)建多個等于1的引用時花枫,實際上是讓所有這些引用指向同一個對象刻盐。為了檢驗兩個引用指向同一個對象,我們可以用is關(guān)鍵字劳翰。is用于判斷兩個引用所指向的對象是否相同敦锌。
print (a is b)? #True
在Python中,整數(shù)和短小的字符佳簸,Python都會緩存這些對象乙墙,以便重復使用。賦值語句生均,只是創(chuàng)造了新的引用听想,而不是對象本身。長的字符串和其它對象可以有多個相同的對象马胧,可以使用賦值語句創(chuàng)建出新的對象汉买。每個對象都有存有指向該對象的引用總數(shù),即引用計數(shù)(reference count)漓雅。?
可以使用sys.getrefcount()獲得引用計數(shù)录别,需要注意的是朽色,當使用某個引用作為參數(shù)邻吞,傳遞給getrefcount()時,參數(shù)實際上創(chuàng)建了一個臨時的引用葫男。因此抱冷,getrefcount()所得到的結(jié)果,會比期望的多1梢褐。
from sys import getrefcount
a = [1,2,3]
print(getrefcount(a))? # 2
b = a
print(getrefcount(b))? # 3
引用計數(shù)增加?
1.對象被創(chuàng)建:x=4?
2.另外的別人被創(chuàng)建:y=x?
3.被作為參數(shù)傳遞給函數(shù):foo(x)?
4.作為容器對象的一個元素:a=[1, x, ‘33’]
引用計數(shù)減少?
1.一個本地引用離開了它的作用域旺遮。比如上面的foo(x)函數(shù)結(jié)束時,x指向的對象引用減1盈咳。?
2.對象的別名被顯式的銷毀:del x 耿眉;或者del y?
3.對象的一個別名被賦值給其他對象:x=789?
4.對象從一個窗口對象中移除:myList.remove(x)?
5.窗口對象本身被銷毀:del myList,或者窗口對象本身離開了作用域鱼响。
垃圾回收
引用計數(shù)?
引用計數(shù)也是一種垃圾收集機制鸣剪,而且也是一種最直觀,最簡單的垃圾收集技術(shù)。當Python的某個對象的引用計數(shù)降為0時筐骇,說明沒有任何引用指向該對象债鸡,該對象就成為要被回收的垃圾了。比如某個新建對象铛纬,它被分配給某個引用厌均,對象的引用計數(shù)變?yōu)?。如果引用被刪除告唆,對象的引用計數(shù)為0棺弊,那么該對象就可以被垃圾回收。?
不過如果出現(xiàn)循環(huán)引用的話悔详,引用計數(shù)機制就不再起有效的作用了
a = [ ]
b = [ ]
a.append(b)
b.append(a)
print a? # [[[…]]]
print b? # [[[…]]]
循環(huán)引用可以使一組對象的引用計數(shù)不為0镊屎,然而這些對象實際上并沒有被任何外部對象所引用,它們之間只是相互引用茄螃。這意味著不會再有人使用這組對象缝驳,應該回收這組對象所占用的內(nèi)存空間,然后由于相互引用的存在归苍,每一個對象的引用計數(shù)都不為0用狱,因此這些對象所占用的內(nèi)存永遠不會被釋放。?
Python又引入了其他的垃圾收集機制來彌補引用計數(shù)的缺陷:“標記-清除“拼弃,“分代回收”兩種收集技術(shù)夏伊。
標記清除?
如果兩個對象的引用計數(shù)都為1,但是僅僅存在他們之間的循環(huán)引用吻氧,那么這兩個對象都是需要被回收的溺忧,也就是說,它們的引用計數(shù)雖然表現(xiàn)為非0盯孙,但實際上有效的引用計數(shù)為0鲁森。所以先將循環(huán)引用摘掉,就會得出這兩個對象的有效計數(shù)振惰。?
在實際操作中歌溉,并不改動真實的引用計數(shù),而是將集合中對象的引用計數(shù)復制一份副本骑晶,改動該對象引用的副本痛垛。對于副本做任何的改動,都不會影響到對象生命周期的維護桶蛔。?
這個計數(shù)副本的唯一作用是尋找root object集合(該集合中的對象是不能被回收的)匙头。當成功尋找到root object集合之后,首先將現(xiàn)在的內(nèi)存鏈表一分為二仔雷,一條鏈表中維護root object集合蹂析,成為root鏈表抖剿,而另外一條鏈表中維護剩下的對象,成為unreachable鏈表识窿。之所以要剖成兩個鏈表斩郎,是基于這樣的一種考慮:現(xiàn)在的unreachable可能存在被root鏈表中的對象,直接或間接引用的對象喻频,這些對象是不能被回收的缩宜,一旦在標記的過程中,發(fā)現(xiàn)這樣的對象甥温,就將其從unreachable鏈表中移到root鏈表中锻煌;當完成標記后,unreachable鏈表中剩下的所有對象就是名副其實的垃圾對象了姻蚓,接下來的垃圾回收只需限制在unreachable鏈表中即可宋梧。
分代回收?
從前面“標記-清除”這樣的垃圾收集機制來看,這種垃圾收集機制所帶來的額外操作實際上與系統(tǒng)中總的內(nèi)存塊的數(shù)量是相關(guān)的狰挡,當需要回收的內(nèi)存塊越多時捂龄,垃圾檢測帶來的額外操作就越多,而垃圾回收帶來的額外操作就越少加叁;反之倦沧,當需回收的內(nèi)存塊越少時,垃圾檢測就將比垃圾回收帶來更少的額外操作它匕。?
舉個例子來說明:?
當某些內(nèi)存塊M經(jīng)過了3次垃圾收集的清洗之后還存活時展融,我們就將內(nèi)存塊M劃到一個集合A中去,而新分配的內(nèi)存都劃分到集合B中去豫柬。當垃圾收集開始工作時告希,大多數(shù)情況都只對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間后才進行烧给,這就使得垃圾收集機制需要處理的內(nèi)存少了燕偶,效率自然就提高了。在這個過程中创夜,集合B中的某些內(nèi)存塊由于存活時間長而會被轉(zhuǎn)移到集合A中杭跪,當然仙逻,集合A中實際上也存在一些垃圾驰吓,這些垃圾的回收會因為這種分代的機制而被延遲。
內(nèi)存池
Python內(nèi)部默認的小塊內(nèi)存與大塊內(nèi)存的分界點定在256個字節(jié)系奉,當申請的內(nèi)存小于256字節(jié)時檬贰,PyObject_Malloc會在內(nèi)存池中申請內(nèi)存;當申請的內(nèi)存大于256字節(jié)時缺亮,PyObject_Malloc的行為將蛻化為malloc的行為翁涤。當然,通過修改Python源代碼,我們可以改變這個默認值葵礼,從而改變Python的默認內(nèi)存管理行為号阿。