面試必備:Python內(nèi)存管理機(jī)制(建議收藏)

什么是內(nèi)存管理器(what)

Python作為一個(gè)高層次的結(jié)合了解釋性、編譯性合溺、互動(dòng)性和面向?qū)ο蟮哪_本語言羹幸,與大多數(shù)編程語言不同,Python中的變量無需事先申明辫愉,變量無需指定類型栅受,程序員無需關(guān)心內(nèi)存管理,Python解釋器給你自動(dòng)回收恭朗。開發(fā)人員不用過多的關(guān)心內(nèi)存管理機(jī)制屏镊,這一切全部由python內(nèi)存管理器承擔(dān)了復(fù)雜的內(nèi)存管理工作。

內(nèi)存不外乎創(chuàng)建和銷毀兩部分痰腮,本文將圍繞python的內(nèi)存池和垃圾回收兩部分進(jìn)行分析而芥。

如果大家在學(xué)習(xí)中遇到困難,想找一個(gè)python學(xué)習(xí)交流環(huán)境膀值,可以加入我們的python裙棍丐,裙號(hào)930900780,可領(lǐng)取python學(xué)習(xí)資料沧踏,會(huì)節(jié)約很多時(shí)間歌逢,減少很多遇到的難題。

Python內(nèi)存池

為什么要引入內(nèi)存池(why)

當(dāng)創(chuàng)建大量消耗小內(nèi)存的對(duì)象時(shí)翘狱,頻繁調(diào)用new/malloc會(huì)導(dǎo)致大量的內(nèi)存碎片秘案,致使效率降低。內(nèi)存池的作用就是預(yù)先在內(nèi)存中申請(qǐng)一定數(shù)量的潦匈,大小相等的內(nèi)存塊留作備用阱高,當(dāng)有新的內(nèi)存需求時(shí),就先從內(nèi)存池中分配內(nèi)存給這個(gè)需求茬缩,不夠之后再申請(qǐng)新的內(nèi)存赤惊。這樣做最顯著的優(yōu)勢(shì)就是能夠減少內(nèi)存碎片,提升效率凰锡。

python中的內(nèi)存管理機(jī)制為Pymalloc

內(nèi)存池是如果工作的(how)

首先未舟,我們看一張CPython(python解釋器)的內(nèi)存架構(gòu)圖:

python的對(duì)象管理主要位于Level+1~Level+3層

Level+3層:對(duì)于python內(nèi)置的對(duì)象(比如int,dict等)都有獨(dú)立的私有內(nèi)存池,對(duì)象之間的內(nèi)存池不共享寡夹,即int釋放的內(nèi)存处面,不會(huì)被分配給float使用

Level+2層:當(dāng)申請(qǐng)的內(nèi)存大小小于256KB時(shí)厂置,內(nèi)存分配主要由 Python 對(duì)象分配器(Python’s object allocator)實(shí)施

Level+1層:當(dāng)申請(qǐng)的內(nèi)存大小大于256KB時(shí)菩掏,由Python原生的內(nèi)存分配器進(jìn)行分配,本質(zhì)上是調(diào)用C標(biāo)準(zhǔn)庫中的malloc/realloc等函數(shù)

關(guān)于釋放內(nèi)存方面昵济,當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)變?yōu)?時(shí)智绸,Python就會(huì)調(diào)用它的析構(gòu)函數(shù)野揪。調(diào)用析構(gòu)函數(shù)并不意味著最終一定會(huì)調(diào)用free來釋放內(nèi)存空間,如果真是這樣的話瞧栗,那頻繁地申請(qǐng)、釋放內(nèi)存空間會(huì)使Python的執(zhí)行效率大打折扣。因此在析構(gòu)時(shí)也采用了內(nèi)存池機(jī)制驳棱,從內(nèi)存池申請(qǐng)到的內(nèi)存會(huì)被歸還到內(nèi)存池中塘安,以避免頻繁地申請(qǐng)和釋放動(dòng)作。

垃圾回收機(jī)制

Python的垃圾回收機(jī)制采用引用計(jì)數(shù)機(jī)制為主殴边,標(biāo)記-清除和分代回收機(jī)制為輔的策略憎茂。其中,標(biāo)記-清除機(jī)制用來解決計(jì)數(shù)引用帶來的循環(huán)引用而無法釋放內(nèi)存的問題锤岸,分代回收機(jī)制是為提升垃圾回收的效率竖幔。

引用計(jì)數(shù)

Python通過引用計(jì)數(shù)來保存內(nèi)存中的變量追蹤,即記錄該對(duì)象被其他使用的對(duì)象引用的次數(shù)是偷。

Python中有個(gè)內(nèi)部跟蹤變量叫做引用計(jì)數(shù)器拳氢,每個(gè)變量有多少個(gè)引用,簡稱引用計(jì)數(shù)蛋铆。當(dāng)某個(gè)對(duì)象的引用計(jì)數(shù)為0時(shí)馋评,就列入了垃圾回收隊(duì)列。

>>>a=[1,2]

>>>importsys

>>>sys.getrefcount(a)## 獲取對(duì)象a的引用次數(shù)

2

>>>b=a

>>>sys.getrefcount(a)

3

>>>delb## 刪除b的引用

>>>sys.getrefcount(a)

2

>>>c=list()

>>>c.append(a)## 加入到容器中

>>>sys.getrefcount(a)

3

>>>delc## 刪除容器刺啦,引用-1

>>>sys.getrefcount(a)

2

>>>b=a

>>>sys.getrefcount(a)

3

>>>a=[3,4]## 重新賦值

>>>sys.getrefcount(a)

2

復(fù)制代碼

注意:當(dāng)把a(bǔ)作為參數(shù)傳遞給getrefcount時(shí)栗恩,會(huì)產(chǎn)生一個(gè)臨時(shí)的引用,因此得出來的結(jié)果比真實(shí)情況+1

引用計(jì)數(shù)增加的情況:

一個(gè)對(duì)象被分配給一個(gè)新的名字(例如:a=[1,2])

將其放入一個(gè)容器中(如列表洪燥、元組或字典)(例如:c.append(a))

引用計(jì)數(shù)減少的情況:

使用del語句對(duì)對(duì)象別名顯式的銷毀(例如:del b)

對(duì)象所在的容器被銷毀或從容器中刪除對(duì)象(例如:del c )

引用超出作用域或被重新賦值(例如:a=[3,4])

引用計(jì)數(shù)能夠解決大多數(shù)垃圾回收的問題磕秤,但是遇到兩個(gè)對(duì)象相互引用的情況,del語句可以減少引用次數(shù)捧韵,但是引用計(jì)數(shù)不會(huì)歸0市咆,對(duì)象也就不會(huì)被銷毀,從而造成了內(nèi)存泄漏問題再来。針對(duì)該情況蒙兰,Python引入了標(biāo)記-清除機(jī)制

標(biāo)記-清除

標(biāo)記-清除用來解決引用計(jì)數(shù)機(jī)制產(chǎn)生的循環(huán)引用芒篷,進(jìn)而導(dǎo)致內(nèi)存泄漏的問題 搜变。 循環(huán)引用只有在容器對(duì)象才會(huì)產(chǎn)生,比如字典针炉,元組挠他,列表等。

顧名思義篡帕,該機(jī)制在進(jìn)行垃圾回收時(shí)分成了兩步殖侵,分別是:

標(biāo)記階段贸呢,遍歷所有的對(duì)象,如果是可達(dá)的(reachable)拢军,也就是還有對(duì)象引用它楞陷,那么就標(biāo)記該對(duì)象為可達(dá)

清除階段,再次遍歷對(duì)象茉唉,如果發(fā)現(xiàn)某個(gè)對(duì)象沒有標(biāo)記為可達(dá)(即為Unreachable)固蛾,則就將其回收

>>>a=[1,2]

>>>b=[3,4]

>>>sys.getrefcount(a)2

>>>sys.getrefcount(b)2

>>>a.append(b)>>> sys.getrefcount(b)3

>>>b.append(a)>>> sys.getrefcount(a)3

>>>dela

>>>delb

復(fù)制代碼

a引用b,b引用a,此時(shí)兩個(gè)對(duì)象各自被引用了2次(去除getrefcout()的臨時(shí)引用)

執(zhí)行del之后,對(duì)象a,b的引用次數(shù)都-1度陆,此時(shí)各自的引用計(jì)數(shù)器都為1魏铅,陷入循環(huán)引用

標(biāo)記:找到其中的一端a,因?yàn)樗幸粋€(gè)對(duì)b的引用,則將b的引用計(jì)數(shù)-1


標(biāo)記:再沿著引用到b,b有一個(gè)a的引用,將a的引用計(jì)數(shù)-1坚芜,此時(shí)對(duì)象a和b的引用次數(shù)全部為0览芳,被標(biāo)記為不可達(dá)(Unreachable)

清除: 被標(biāo)記為不可達(dá)的對(duì)象就是真正需要被釋放的對(duì)象

上面描述的垃圾回收的階段,會(huì)暫停整個(gè)應(yīng)用程序鸿竖,等待標(biāo)記清除結(jié)束后才會(huì)恢復(fù)應(yīng)用程序的運(yùn)行沧竟。為了減少應(yīng)用程序暫停的時(shí)間,Python 通過“分代回收”(Generational Collection)以空間換時(shí)間的方法提高垃圾回收效率缚忧。

分代回收

分代回收是基于這樣的一個(gè)統(tǒng)計(jì)事實(shí)悟泵,對(duì)于程序,存在一定比例的內(nèi)存塊的生存周期比較短闪水;而剩下的內(nèi)存塊糕非,生存周期會(huì)比較長,甚至?xí)某绦蜷_始一直持續(xù)到程序結(jié)束球榆。生存期較短對(duì)象的比例通常在 80%~90%之間朽肥。 因此,簡單地認(rèn)為:對(duì)象存在時(shí)間越長持钉,越可能不是垃圾衡招,應(yīng)該越少去收集。這樣在執(zhí)行標(biāo)記-清除算法時(shí)可以有效減小遍歷的對(duì)象數(shù)每强,從而提高垃圾回收的速度始腾,是一種以空間換時(shí)間的方法策略

Python將所有的對(duì)象分為年輕代(第0代)空执、中年代(第1代)浪箭、老年代(第2代)三代。所有的新建對(duì)象默認(rèn)是 第0代對(duì)象辨绊。當(dāng)在第0代的gc掃描中存活下來的對(duì)象將被移至第1代奶栖,在第1代的gc掃描中存活下來的對(duì)象將被移至第2代。

gc掃描次數(shù)(第0代>第1代>第2代)

當(dāng)某一代中被分配的對(duì)象與被釋放的對(duì)象之差達(dá)到某一閾值時(shí),就會(huì)觸發(fā)當(dāng)前一代的gc掃描驼抹。當(dāng)某一代被掃描時(shí)桑孩,比它年輕的一代也會(huì)被掃描拜鹤,因此框冀,第2代的gc掃描發(fā)生時(shí),第0敏簿,1代的gc掃描也會(huì)發(fā)生明也,即為全代掃描。

>>>importgc

>>>gc.get_threshold()## 分代回收機(jī)制的參數(shù)閾值設(shè)置

(700, 10, 10)

復(fù)制代碼

700=新分配的對(duì)象數(shù)量-釋放的對(duì)象數(shù)量惯裕,第0代gc掃描被觸發(fā)

第一個(gè)10:第0代gc掃描發(fā)生10次温数,則第1代的gc掃描被觸發(fā)

第二個(gè)10:第1代的gc掃描發(fā)生10次,則第2代的gc掃描被觸發(fā)

思考

在標(biāo)記-清除中蜻势,如果對(duì)象c也引用a,執(zhí)行del操作后撑刺,會(huì)發(fā)生什么?

對(duì)象a,b,c的引用關(guān)系如下圖所示:

>>> a=[1,2]

>>> b=[3,4]

>>> c=a>>> a.append(b)

>>> b.append(a)

復(fù)制代碼

ref_count表示引用計(jì)數(shù)

對(duì)象a,b,c全部為reachable

執(zhí)行del之后握玛,引用關(guān)系如下圖所示:

>>>dela

>>>delb

復(fù)制代碼

a,b,c的ref_count減1

執(zhí)行g(shù)c掃描

標(biāo)記: a引用b,將b的ref_count減1到0够傍,b引用a,將a的ref_count減1到1,將b放在unreachable下

再循環(huán):因?yàn)閍是可達(dá)的挠铲,所以會(huì)遞歸地將從a節(jié)點(diǎn)出發(fā)可以達(dá)到的所有節(jié)點(diǎn)標(biāo)記為reachable下冕屯,即為:

清除:unreachable下沒有可清除的對(duì)象,因此a,b,c對(duì)象不會(huì)被清除

總結(jié)

總體而言拂苹,python通過內(nèi)存池來減少內(nèi)存碎片化安聘,提高執(zhí)行效率。主要通過引用計(jì)數(shù)來完成垃圾回收瓢棒,通過標(biāo)記-清除解決容器對(duì)象循環(huán)引用造成的問題浴韭,通過分代回收提高垃圾回收的效率。


最后多說一句脯宿,小編是一名python開發(fā)工程師囱桨,這里有我自己整理了一套最新的python系統(tǒng)學(xué)習(xí)教程,包括從基礎(chǔ)的python腳本到web開發(fā)嗅绰、爬蟲舍肠、數(shù)據(jù)分析、數(shù)據(jù)可視化窘面、機(jī)器學(xué)習(xí)等翠语。想要這些資料的可以進(jìn)裙930900780領(lǐng)取。


本文章素材來源于網(wǎng)絡(luò)财边,如有侵權(quán)請(qǐng)聯(lián)系刪除肌括。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谍夭,更是在濱河造成了極大的恐慌黑滴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紧索,死亡現(xiàn)場(chǎng)離奇詭異袁辈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)珠漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門晚缩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人媳危,你說我怎么就攤上這事荞彼。” “怎么了待笑?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵鸣皂,是天一觀的道長。 經(jīng)常有香客問我暮蹂,道長寞缝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任椎侠,我火速辦了婚禮第租,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘我纪。我一直安慰自己慎宾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布浅悉。 她就那樣靜靜地躺著趟据,像睡著了一般。 火紅的嫁衣襯著肌膚如雪术健。 梳的紋絲不亂的頭發(fā)上汹碱,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音荞估,去河邊找鬼咳促。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勘伺,可吹牛的內(nèi)容都是我干的跪腹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼飞醉,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冲茸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤轴术,失蹤者是張志新(化名)和其女友劉穎难衰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逗栽,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盖袭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祭陷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苍凛。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趣席,死狀恐怖兵志,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宣肚,我是刑警寧澤想罕,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站霉涨,受9級(jí)特大地震影響按价,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笙瑟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一楼镐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧往枷,春花似錦框产、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屯碴,卻和暖如春描睦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背导而。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工忱叭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人今艺。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓韵丑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親洼滚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埂息,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345