參考:http://www.cnblogs.com/CBDoctor/p/3781078.html
先從較淺的層面來(lái)說(shuō)弃甥,Python的內(nèi)存管理機(jī)制可以從三個(gè)方面來(lái)講
(1)垃圾回收
(2)引用計(jì)數(shù)
(3)內(nèi)存池機(jī)制
一、垃圾回收:
Python不像C++汁讼,Java等語(yǔ)言一樣淆攻,他們可以不用事先聲明變量類型而直接對(duì)變量進(jìn)行賦值。對(duì)Python語(yǔ)言來(lái)講嘿架,對(duì)象的類型和內(nèi)存都是在運(yùn)行時(shí)確定的瓶珊。這也是為什么我們稱Python語(yǔ)言為動(dòng)態(tài)類型的原因(這里我們把動(dòng)態(tài)類型可以簡(jiǎn)單的歸結(jié)為對(duì)變量?jī)?nèi)存地址的分配是在運(yùn)行時(shí)自動(dòng)判斷變量類型并對(duì)變量進(jìn)行賦值)。
二、引用計(jì)數(shù):
Python采用了類似Windows內(nèi)核對(duì)象一樣的方式來(lái)對(duì)內(nèi)存進(jìn)行管理。每一個(gè)對(duì)象怯邪,都維護(hù)這一個(gè)對(duì)指向該對(duì)對(duì)象的引用的計(jì)數(shù)。如圖所示(圖片來(lái)自Python核心編程)
x = 3.14
y = x
我們首先創(chuàng)建了一個(gè)對(duì)象3.14, 然后將這個(gè)浮點(diǎn)數(shù)對(duì)象的引用賦值給x召川,因?yàn)閤是第一個(gè)引用南缓,因此,這個(gè)浮點(diǎn)數(shù)對(duì)象的引用計(jì)數(shù)為1. 語(yǔ)句y = x創(chuàng)建了一個(gè)指向同一個(gè)對(duì)象的引用別名y荧呐,我們發(fā)現(xiàn)汉形,并沒(méi)有為Y創(chuàng)建一個(gè)新的對(duì)象,而是將Y也指向了x指向的浮點(diǎn)數(shù)對(duì)象倍阐,使其引用計(jì)數(shù)為2.
我們可以很容易就證明上述的觀點(diǎn):
變量a 和 變量b的id一致(我們可以將id值想象為C中變量的指針).
我們?cè)硪粋€(gè)網(wǎng)址的圖片來(lái)說(shuō)明問(wèn)題:對(duì)于C語(yǔ)言來(lái)講概疆,我們創(chuàng)建一個(gè)變量A時(shí)就會(huì)為為該變量申請(qǐng)一個(gè)內(nèi)存空間,并將變量值 放入該空間中,當(dāng)將該變量賦給另一變量B時(shí)會(huì)為B申請(qǐng)一個(gè)新的內(nèi)存空間峰搪,并將變量值放入到B的內(nèi)存空間中岔冀,這也是為什么A和B的指針不一致的原因。如圖:
int A = 1 int A = 2
而Python的情況卻不一樣概耻,實(shí)際上使套,Python的處理方式和JavaScript有點(diǎn)類似罐呼,如圖所示,變量更像是附在對(duì)象上的標(biāo)簽(和引用的定義類似)童漩。當(dāng)變量被綁定在一個(gè)對(duì)象上的時(shí)候,該變量的引用計(jì)數(shù)就是1春锋,(還有另外一些情況也會(huì)導(dǎo)致變量引用計(jì)數(shù)的增加),系統(tǒng)會(huì)自動(dòng)維護(hù)這些標(biāo)簽矫膨,并定時(shí)掃描,當(dāng)某標(biāo)簽的引用計(jì)數(shù)變?yōu)?的時(shí)候期奔,該對(duì)就會(huì)被回收侧馅。
a = 1 a = 2 b = a
三、內(nèi)存池機(jī)制
Python的內(nèi)存機(jī)制以金字塔行呐萌,-1馁痴,-2層主要有操作系統(tǒng)進(jìn)行操作,
第0層是C中的malloc肺孤,free等內(nèi)存分配和釋放函數(shù)進(jìn)行操作罗晕;
第1層和第2層是內(nèi)存池,有Python的接口函數(shù)PyMem_Malloc函數(shù)實(shí)現(xiàn)赠堵,當(dāng)對(duì)象小于256K時(shí)有該層直接分配內(nèi)存小渊;
第3層是最上層,也就是我們對(duì)Python對(duì)象的直接操作茫叭;
在 C 中如果頻繁的調(diào)用 malloc 與 free 時(shí),是會(huì)產(chǎn)生性能問(wèn)題的.再加上頻繁的分配與釋放小塊的內(nèi)存會(huì)產(chǎn)生內(nèi)存碎片. Python 在這里主要干的工作有:
如果請(qǐng)求分配的內(nèi)存在1~256字節(jié)之間就使用自己的內(nèi)存管理系統(tǒng),否則直接使用 malloc.
這里還是會(huì)調(diào)用 malloc 分配內(nèi)存,但每次會(huì)分配一塊大小為256k的大塊內(nèi)存.
經(jīng)由內(nèi)存池登記的內(nèi)存到最后還是會(huì)回收到內(nèi)存池,并不會(huì)調(diào)用 C 的 free 釋放掉.以便下次使用.對(duì)于簡(jiǎn)單的Python對(duì)象酬屉,例如數(shù)值、字符串揍愁,元組(tuple不允許被更改)采用的是復(fù)制的方式(深拷貝?)呐萨,也就是說(shuō)當(dāng)將另一個(gè)變量B賦值給變量A時(shí),雖然A和B的內(nèi)存空間仍然相同莽囤,但當(dāng)A的值發(fā)生變化時(shí)谬擦,會(huì)重新給A分配空間,A和B的地址變得不再相同
而對(duì)于像字典(dict)朽缎,列表(List)等怯屉,改變一個(gè)就會(huì)引起另一個(gè)的改變,也稱之為淺拷貝
附:
引用計(jì)數(shù)增加
1.對(duì)象被創(chuàng)建:x=4
2.另外的別人被創(chuàng)建:y=x
3.被作為參數(shù)傳遞給函數(shù):foo(x)
4.作為容器對(duì)象的一個(gè)元素:a=[1,x,'33']
引用計(jì)數(shù)減少
1.一個(gè)本地引用離開(kāi)了它的作用域饵沧。比如上面的foo(x)函數(shù)結(jié)束時(shí)锨络,x指向的對(duì)象引用減1。
2.對(duì)象的別名被顯式的銷毀:del x 狼牺;或者del y
3.對(duì)象的一個(gè)別名被賦值給其他對(duì)象:x=789
4.對(duì)象從一個(gè)窗口對(duì)象中移除:myList.remove(x)
5.窗口對(duì)象本身被銷毀:del myList羡儿,或者窗口對(duì)象本身離開(kāi)了作用域。
垃圾回收
1是钥、當(dāng)內(nèi)存中有不再使用的部分時(shí)掠归,垃圾收集器就會(huì)把他們清理掉缅叠。它會(huì)去檢查那些引用計(jì)數(shù)為0的對(duì)象,然后清除其在內(nèi)存的空間虏冻。當(dāng)然除了引用計(jì)數(shù)為0的會(huì)被清除肤粱,還有一種情況也會(huì)被垃圾收集器清掉:當(dāng)兩個(gè)對(duì)象相互引用時(shí),他們本身其他的引用已經(jīng)為0了厨相。
2领曼、垃圾回收機(jī)制還有一個(gè)循環(huán)垃圾回收器, 確保釋放循環(huán)引用對(duì)象(a引用b, b引用a, 導(dǎo)致其引用計(jì)數(shù)永遠(yuǎn)不為0)。
以下摘自vamei:http://www.cnblogs.com/vamei/p/3232088.html
在Python中蛮穿,整數(shù)和短小的字符庶骄,Python都會(huì)緩存這些對(duì)象,以便重復(fù)使用践磅。當(dāng)我們創(chuàng)建多個(gè)等于1的引用時(shí)单刁,實(shí)際上是讓所有這些引用指向同一個(gè)對(duì)象。
a = 1b = 1print(id(a))print(id(b))
上面程序返回
11246696
11246696
可見(jiàn)a和b實(shí)際上是指向同一個(gè)對(duì)象的兩個(gè)引用府适。
為了檢驗(yàn)兩個(gè)引用指向同一個(gè)對(duì)象羔飞,我們可以用is關(guān)鍵字。is用于判斷兩個(gè)引用所指的對(duì)象是否相同檐春。
Truea = 1b = 1print(a is b)# Truea = "good"b = "good"print(a is b)# Falsea = "very good morning"b = "very good morning"print(a is b)# Falsea = []b = []print(a is b)
上面的注釋為相應(yīng)的運(yùn)行結(jié)果褥傍。可以看到喇聊,由于Python緩存了整數(shù)和短字符串恍风,因此每個(gè)對(duì)象只存有一份。比如誓篱,所有整數(shù)1的引用都指向同一對(duì)象朋贬。即使使用賦值語(yǔ)句,也只是創(chuàng)造了新的引用窜骄,而不是對(duì)象本身锦募。長(zhǎng)的字符串和其它對(duì)象可以有多個(gè)相同的對(duì)象,可以使用賦值語(yǔ)句創(chuàng)建出新的對(duì)象邻遏。
在Python中糠亩,每個(gè)對(duì)象都有存有指向該對(duì)象的引用總數(shù),即引用計(jì)數(shù)(reference count)准验。
我們可以使用sys包中的getrefcount()赎线,來(lái)查看某個(gè)對(duì)象的引用計(jì)數(shù)。需要注意的是糊饱,當(dāng)使用某個(gè)引用作為參數(shù)垂寥,傳遞給getrefcount()時(shí),參數(shù)實(shí)際上創(chuàng)建了一個(gè)臨時(shí)的引用。因此滞项,getrefcount()所得到的結(jié)果狭归,會(huì)比期望的多1。
from sys import getrefcounta = [1, 2, 3]print(getrefcount(a))b = aprint(getrefcount(b))
由于上述原因文判,兩個(gè)getrefcount將返回2和3过椎,而不是期望的1和2。
對(duì)象引用對(duì)象
Python的一個(gè)容器對(duì)象(Container)戏仓,比如表疚宇、詞典等,可以包含多個(gè)對(duì)象柜去。實(shí)際上灰嫉,容器對(duì)象中包含的并不是元素對(duì)象本身拆宛,是指向各個(gè)元素對(duì)象的引用嗓奢。
我們也可以自定義一個(gè)對(duì)象,并引用其它對(duì)象:
class from_obj(object): def init(self, to_obj): self.to_obj = to_objb = [1,2,3]a = from_obj(b)print(id(a.to_obj))print(id(b))
可以看到浑厚,a引用了對(duì)象b股耽。
對(duì)象引用對(duì)象,是Python最基本的構(gòu)成方式钳幅。即使是a = 1這一賦值方式物蝙,實(shí)際上是讓詞典的一個(gè)鍵值"a"的元素引用整數(shù)對(duì)象1。該詞典對(duì)象用于記錄所有的全局引用敢艰。該詞典引用了整數(shù)對(duì)象1诬乞。我們可以通過(guò)內(nèi)置函數(shù)globals()來(lái)查看該詞典。
當(dāng)一個(gè)對(duì)象A被另一個(gè)對(duì)象B引用時(shí)钠导,A的引用計(jì)數(shù)將增加1震嫉。
from sys import getrefcounta = [1, 2, 3]print(getrefcount(a))b = [a, a]print(getrefcount(a))
由于對(duì)象b引用了兩次a,a的引用計(jì)數(shù)增加了2牡属。
當(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ì)象不多侥加,就沒(méi)有必要總啟動(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)朋魔。
我們可以通過(guò)gc模塊的get_threshold()方法,查看該閾值:
import gcprint(gc.get_threshold())
返回(700, 10, 10)卿操,后面的兩個(gè)10是與分代回收相關(guān)的閾值警检,后面可以看到。700即是垃圾回收啟動(dòng)的閾值害淤∩鹊瘢可以通過(guò)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)歷過(guò)垃圾回收,依然存活盆色,那么它就被歸入下一代對(duì)象灰蛙。垃圾回收啟動(dòng)時(shí),一定會(huì)掃描所有的0代對(duì)象隔躲。如果0代經(jīng)過(guò)一定次數(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耗溜。也就是說(shuō),每10次0代垃圾回收省容,會(huì)配合1次1代的垃圾回收抖拴;而每10次1代的垃圾回收,才會(huì)有1次的2代垃圾回收腥椒。
同樣可以用set_threshold()來(lái)調(diào)整阿宅,比如對(duì)2代對(duì)象進(jìn)行更頻繁的掃描。
import gcgc.set_threshold(700, 10, 5)