【轉(zhuǎn)】Python的內(nèi)存管理以及垃圾回收

參考: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ì)象是否相同檐春。


復(fù)制代碼

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)

復(fù)制代碼

上面的注釋為相應(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。


復(fù)制代碼

from sys import getrefcounta = [1, 2, 3]print(getrefcount(a))b = aprint(getrefcount(b))


復(fù)制代碼

由于上述原因文判,兩個(gè)getrefcount將返回2和3过椎,而不是期望的1和2。

對(duì)象引用對(duì)象
Python的一個(gè)容器對(duì)象(Container)戏仓,比如表疚宇、詞典等,可以包含多個(gè)對(duì)象柜去。實(shí)際上灰嫉,容器對(duì)象中包含的并不是元素對(duì)象本身拆宛,是指向各個(gè)元素對(duì)象的引用嗓奢。
我們也可以自定義一個(gè)對(duì)象,并引用其它對(duì)象:

復(fù)制代碼

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))
復(fù)制代碼

可以看到浑厚,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震嫉。


復(fù)制代碼

from sys import getrefcounta = [1, 2, 3]print(getrefcount(a))b = [a, a]print(getrefcount(a))


復(fù)制代碼

由于對(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笼蛛,一起剝皮案震驚了整個(gè)濱河市洒放,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滨砍,老刑警劉巖往湿,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惨好,居然都是意外死亡煌茴,警方通過(guò)查閱死者的電腦和手機(jī)随闺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門日川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人矩乐,你說(shuō)我怎么就攤上這事龄句。” “怎么了散罕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵分歇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我欧漱,道長(zhǎng)职抡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任误甚,我火速辦了婚禮缚甩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窑邦。我一直安慰自己擅威,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布冈钦。 她就那樣靜靜地躺著郊丛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厉熟,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天导盅,我揣著相機(jī)與錄音,去河邊找鬼揍瑟。 笑死认轨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的月培。 我是一名探鬼主播嘁字,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杉畜!你這毒婦竟也來(lái)了纪蜒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤此叠,失蹤者是張志新(化名)和其女友劉穎纯续,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體灭袁,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猬错,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茸歧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倦炒。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖软瞎,靈堂內(nèi)的尸體忽然破棺而出逢唤,到底是詐尸還是另有隱情,我是刑警寧澤涤浇,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布鳖藕,位于F島的核電站,受9級(jí)特大地震影響只锭,放射性物質(zhì)發(fā)生泄漏著恩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一蜻展、第九天 我趴在偏房一處隱蔽的房頂上張望喉誊。 院中可真熱鬧,春花似錦铺呵、人聲如沸裹驰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幻林。三九已至贞盯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沪饺,已是汗流浹背躏敢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留整葡,地道東北人件余。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像遭居,于是被迫代替她去往敵國(guó)和親啼器。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容