一、Python/ target=_blank class=infotextkey>Python內(nèi)存管理
這個(gè)問題需要從三個(gè)方面來說:
- 1)對(duì)象的引用計(jì)數(shù)機(jī)制(四增五減)
- 2)垃圾回收機(jī)制(手動(dòng)自動(dòng),分代回收)
- 3)內(nèi)存池機(jī)制(大m小p)
1)對(duì)象的引用計(jì)數(shù)機(jī)制
要保持追蹤內(nèi)存中的對(duì)象,Python使用了引用計(jì)數(shù)這一簡(jiǎn)單的技術(shù)。sys.getrefcount(a)可以查看a對(duì)象的引用計(jì)數(shù),但是比正常計(jì)數(shù)大1,因?yàn)檎{(diào)用函數(shù)的時(shí)候傳入a,這會(huì)讓a的引用計(jì)數(shù)+1
a)增加引用計(jì)數(shù)
對(duì)象被創(chuàng)建:x = 3.14
另外的別名被創(chuàng)建:y = x
對(duì)象被作為參數(shù)傳遞給函數(shù)(新的本地引用):foobar(x)
對(duì)象成為容器對(duì)象的一個(gè)元素:myList = [123, x, ‘xyz’]
b)減少引用計(jì)數(shù)
對(duì)象的一個(gè)別名被賦值給其他對(duì)象:x = 123
對(duì)象的別名被顯式銷毀:del y
一個(gè)本地引用離開了其作用范圍。如fooc()函數(shù)結(jié)束時(shí),func函數(shù)中的局部變量(全局變量不會(huì))
對(duì)象被從一個(gè)窗口對(duì)象中移除:myList.remove(x)
窗口對(duì)象本身被銷毀:del myList
c)引用計(jì)數(shù)例子,加深理解
id()獲取對(duì)象的內(nèi)存地址
在Python中,整數(shù)和短小的字符,Python都會(huì)緩存這些對(duì)象,以便重復(fù)使用。當(dāng)我們創(chuàng)建多個(gè)等于1的引用時(shí),實(shí)際上是讓所有這些引用指向同一個(gè)對(duì)象
讓我們來看看較長(zhǎng)的字符串:
sys.getrefcount()來獲取對(duì)象的引用計(jì)數(shù):
2)垃圾回收機(jī)制
吃太多,總會(huì)變胖,Python也是這樣。當(dāng)Python中的對(duì)象越來越多,它們將占據(jù)越來越大的內(nèi)存。不過你不用太擔(dān)心Python的體形,它會(huì)在適當(dāng)?shù)臅r(shí)候“減肥”,啟動(dòng)垃圾回收(garbage collection),將沒用的對(duì)象清除
從基本原理上,當(dāng)Python的某個(gè)對(duì)象的引用計(jì)數(shù)降為0時(shí),說明沒有任何引用指向該對(duì)象,該對(duì)象就成為要被回收的垃圾了
比如某個(gè)新建對(duì)象,它被分配給某個(gè)引用,對(duì)象的引用計(jì)數(shù)變?yōu)?。如果引用被刪除,對(duì)象的引用計(jì)數(shù)為0,那么該對(duì)象就可以被垃圾回收。比如下面的表
del a后,已經(jīng)沒有任何引用指向之前建立的[1, 2, 3]這個(gè)表。這個(gè)對(duì)象如果繼續(xù)待在內(nèi)存里,就成了不健康的脂肪。當(dāng)垃圾回收啟動(dòng)時(shí),Python掃描到這個(gè)引用計(jì)數(shù)為0的對(duì)象,就將它所占據(jù)的內(nèi)存清空
然而,減肥是個(gè)昂貴而費(fèi)力的事情。垃圾回收時(shí),Python不能進(jìn)行其它的任務(wù)。頻繁的垃圾回收將大大降低Python的工作效率。如果內(nèi)存中的對(duì)象不多,就沒有必要總啟動(dòng)垃圾回收
所以,Python只會(huì)在特定條件下,自動(dòng)啟動(dòng)垃圾回收。當(dāng)Python運(yùn)行時(shí),會(huì)記錄其中分配對(duì)象(object allocation)和取消分配對(duì)象(object deallocation)的次數(shù)。當(dāng)兩者的差值高于某個(gè)閾值時(shí),垃圾回收才會(huì)啟動(dòng)
我們可以通過gc模塊的get_threshold()方法,查看該閾值:
返回(700, 10, 10),后面的兩個(gè)10是與分代回收相關(guān)的閾值,后面可以看到。700即是垃圾回收啟動(dòng)的閾值。可以通過gc中的set_threshold()方法重新設(shè)置。
我們也可以手動(dòng)啟動(dòng)垃圾回收,即使用gc.collect()
分代回收:
Python同時(shí)采用了分代(generation)回收的策略。這一策略的基本假設(shè)是,存活時(shí)間越久的對(duì)象,越不可能在后面的程序中變成垃圾。
我們的程序往往會(huì)產(chǎn)生大量的對(duì)象,許多對(duì)象很快產(chǎn)生和消失,但也有一些對(duì)象長(zhǎng)期被使用。出于信任和效率,對(duì)于這樣一些“長(zhǎng)壽”對(duì)象,我們相信它們的用處,所以減少在垃圾回收中掃描它們的頻率
Python將所有的對(duì)象分為0,1,2三代。所有的新建對(duì)象都是0代對(duì)象。當(dāng)某一代對(duì)象經(jīng)歷過垃圾回收,依然存活,那么它就被歸入下一代對(duì)象。垃圾回收啟動(dòng)時(shí),一定會(huì)掃描所有的0代對(duì)象
如果0代經(jīng)過一定次數(shù)垃圾回收,那么就啟動(dòng)對(duì)0代和1代的掃描清理。當(dāng)1代也經(jīng)歷了一定次數(shù)的垃圾回收后,那么會(huì)啟動(dòng)對(duì)0,1,2,即對(duì)所有對(duì)象進(jìn)行掃描
這兩個(gè)次數(shù)即上面get_threshold()返回的(700, 10, 10)返回的兩個(gè)10。也就是說,每10次0代垃圾回收,會(huì)配合1次1代的垃圾回收;而每10次1代的垃圾回收,才會(huì)有1次的2代垃圾回收
同樣可以用set_threshold()來調(diào)整,比如對(duì)2代對(duì)象進(jìn)行更頻繁的掃描
3)內(nèi)存池機(jī)制
Python中有分為大內(nèi)存和小內(nèi)存:(256K為界限分大小內(nèi)存)
1、大內(nèi)存使用malloc進(jìn)行分配
2、小內(nèi)存使用內(nèi)存池進(jìn)行分配
python中的內(nèi)存管理機(jī)制都有兩套實(shí)現(xiàn),一套是針對(duì)小對(duì)象,就是大小小于256K時(shí),pymalloc會(huì)在內(nèi)存池中申請(qǐng)內(nèi)存空間;當(dāng)大于256K時(shí),則會(huì)直接執(zhí)行系統(tǒng)的malloc的行為來申請(qǐng)內(nèi)存空間