目錄
Python內(nèi)存管理
一、對象池
1.小整數(shù)池
系統(tǒng)默認(rèn)創(chuàng)建好得,等著你使用
概述:
整數(shù)在程序中得使用非常廣泛,Python為了優(yōu)化速度,使用了小整數(shù)對象池,避免為整數(shù)頻繁申請和銷毀內(nèi)存空間。
Python 對小整數(shù)得定義是 [-5, 256] ,這些整數(shù)對象是提前建立好得,不會(huì)被垃圾回收。
在一個(gè) Python 得程序中,無論這個(gè)整數(shù)處于LEGB(局部變量,閉包,全局,內(nèi)建模塊)中得哪個(gè)位置,所有位于這個(gè)范圍內(nèi)得整數(shù)使用得都是同一個(gè)對象。
# 交互式環(huán)境下:>>> a = 100>>> b = 100>>> id(a)140720433537792>>> id(b)140720433537792>>> a is bTrue>>>
我們可以看出a,b指向同一個(gè)內(nèi)存地址。
2.大整數(shù)池
大整數(shù)池:默認(rèn)創(chuàng)建出來,池內(nèi)為空得,創(chuàng)建一個(gè)就會(huì)往池中存儲(chǔ)一個(gè)
# python交互式環(huán)境>>> a = 257>>> b = 257>>> id(a)2085029722896>>> id(b)2085029722960>>> a is bFalse>>>
a , b 不是指向同一個(gè)內(nèi)存地址。
python中對大于256得整數(shù),會(huì)重新分配對象空間地址保存對象。
3.inter機(jī)制(短字符串池)
每個(gè)單詞(字符串),不夾雜空格或者其他符號(hào),默認(rèn)開啟intern機(jī)制,共享內(nèi)存,靠引用計(jì)數(shù)決定是否銷毀。
>>> s1 = 'hello'>>> s2 = 'hello'>>> id(s1)2178093449264>>> id(s2)2178093449264>>> s1 is s2True>>>
字符串s1和s2中沒有空格時(shí),可以看出,這里得s1與s2指向同一個(gè)內(nèi)存地址。
當(dāng)我們在he和llo之間加一個(gè)空格
>>> s1 = 'he llo'>>> s2 = 'he llo'>>> id(s1)2278732636592>>> id(s2)2278732636528>>> s1 is s2False>>>
這時(shí)得字符串s1和s2就沒有指向同一個(gè)內(nèi)存地址。
對于字符串來說,如果不包含空格得字符串,則不會(huì)重新分配對象空間,對于包含空格得字符串則會(huì)重新分配對象空間。
二、垃圾回收
概述:
python采用得是引用計(jì)數(shù)機(jī)制為主,隔代回收和標(biāo)記清除機(jī)制為輔得策略
概述:
現(xiàn)在得高級(jí)語言如java,c# 等,都采用了垃圾收集機(jī)制,而不再是c,c++里用戶自己管理維護(hù)內(nèi)存得方式。
自己管理 內(nèi)存極其自由, 可以任意申請內(nèi)存,但如同一把雙刃劍,為大量內(nèi)存泄露,懸空指針等bug埋下隱患。
python里也同java一樣采用了垃圾收集機(jī)制,不過不一樣得是:
python采用得是引用計(jì)數(shù)機(jī)制為主,隔代回收機(jī)制為輔得策略
2.1.引用計(jì)數(shù)
在Python中,每個(gè)對象都有指向該對象得引用總數(shù)---引用計(jì)數(shù)
查看對象得引用計(jì)數(shù):sys.getrefcount()
注意:
當(dāng)使用某個(gè)引用作為參數(shù),傳遞給getrefcount()時(shí),參數(shù)實(shí)際上創(chuàng)建了一個(gè)臨時(shí)得引用。
因此, getrefcount()所得到得結(jié)果,會(huì)比期望得多1。
2.1.1 引用計(jì)數(shù)增加
a、對象被創(chuàng)建
b、另外變量也指向當(dāng)前對象
c、作為容器對象得一個(gè)元素
d、作為參數(shù)提供給函數(shù):test(x)
2.1.2 引用計(jì)數(shù)減少
a、變量被顯式得銷毀
b、對象得另外一個(gè)變量重新賦值
c、從容器中移除
d、函數(shù)被執(zhí)行完畢
看代碼:
# -*- coding: utf-8 -*-import sysclass Test(object): def __init__(self): print('當(dāng)前對象已經(jīng)被創(chuàng)建,占用得內(nèi)存地址為:%s' % hex(id(self)))a = Test()print('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 2b = aprint('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 3list1 = []list1.append(a)print('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 4del bprint('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 3list1.remove(a)print('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 2del aprint('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 報(bào)錯(cuò)'''Traceback (most recent call last): File "E:/Python Project/Python 高級(jí)編程/內(nèi)存管理/垃圾收集.py", line 30, in <module> print('當(dāng)前對象得引用計(jì)數(shù)為:%s' % sys.getrefcount(a))NameError: name 'a' is not defined'''
當(dāng)Python得某個(gè)對象得引用計(jì)數(shù)降為0時(shí),說明沒有任何引用指向該對象,該對象就成為要被回收得垃圾。比如某個(gè)新建對象,被分配給某個(gè)引用,對象得引用計(jì)數(shù)變?yōu)?。如 為0,那么該對象就可以被垃圾回收。
2.2.標(biāo)記清除
標(biāo)記清除(Mark—Sweep)算法是一種基于追蹤回收(tracing GC)技術(shù)實(shí)現(xiàn)得垃圾回收算法。
它分為兩個(gè)階段:
第一階段是標(biāo)記階段,GC會(huì)把所有得活動(dòng)對象打上標(biāo)記
第二階段是把那些沒有標(biāo)記得對象非活動(dòng)對象進(jìn)行回收。
對象之間通過引用(指針)連在一起,構(gòu)成一個(gè)有向圖,對象構(gòu)成這個(gè)有向圖得節(jié)點(diǎn),而引用關(guān)系構(gòu)成這個(gè)有向圖得邊。從根對象(root object)出發(fā),沿著有向邊遍歷對象,可達(dá)得(reachable)對象標(biāo)記為活動(dòng)對象,不可達(dá)得對象就是要被清除得非活動(dòng)對象。根對象就是全局變量、調(diào)用棧、寄存器。
>
在上圖中,可以從程序變量直接訪問塊1,并且可以間接訪問塊2和3。程序無法訪問塊4和5。第一步將標(biāo)記塊1,并記住塊2和3以供稍后處理。第二步將標(biāo)記塊2,第三步將標(biāo)記塊3,但不記得塊2,因?yàn)樗驯粯?biāo)記。掃描階段將忽略塊1,2和3,因?yàn)樗鼈円驯粯?biāo)記,但會(huì)回收塊4和5。
標(biāo)記清除算法作為Python得輔助垃圾收集技術(shù),主要處理得是一些容器對象,比如list、dict、tuple等,因?yàn)閷τ谧址?、?shù)值對象是不可能造成循環(huán)引用問題。Python使用一個(gè)雙向鏈表將這些容器對象組織起來。不過,這種簡單粗暴得標(biāo)記清除算法也有明顯得缺點(diǎn):清除非活動(dòng)得對象前它必須順序掃描整個(gè)堆內(nèi)存,哪怕只剩下小部分活動(dòng)對象也要掃描所有對象。
2.3.分代回收
因?yàn)椋?標(biāo)記和清除得過程效率不高。清除非活動(dòng)得對象前它必須順序掃描整個(gè)堆內(nèi)存,哪怕只剩下小部分活動(dòng)對象也要掃描所有對象。還有一個(gè)問題就是:什么時(shí)候掃描去檢測循環(huán)引用?
為了解決上述得問題,python又引入了分代回收。分代回收解決了標(biāo)記清楚時(shí)什么時(shí)候掃描得問題,并且將掃描得對象分成了3級(jí),以及降低掃描得工作量,提高效率。
- 0代: 0代中對象個(gè)數(shù)達(dá)到700個(gè),掃描一次。
- 1代: 0代掃描10次,則1代掃描1次。
- 2代: 1代掃描10次,則2代掃描1次。
隔代回收是用來解決交叉引用(循環(huán)引用),并增加數(shù)據(jù)回收得效率. 原理:通過對象存在得時(shí)間不同,采用不同得算法來 回收垃圾.
形象得比喻, 三個(gè)鏈表,零代鏈表上得對象(新創(chuàng)建得對象都加入到零代鏈表),引用數(shù)都是一,每增加一個(gè)指針,引用加一,隨后 python會(huì)檢測列表中得互相引用得對象,根據(jù)規(guī)則減掉其引用計(jì)數(shù).
GC算法對鏈表一得引用減一,引用為0得,清除,不為0得到鏈表二,鏈表二也執(zhí)行GC算法,鏈表三一樣. 存在時(shí)間越長得 數(shù)據(jù),越是有用得數(shù)據(jù)
2.3.1 分代回收觸發(fā)時(shí)機(jī)?(GC閾值)
隨著你得程序運(yùn)行,Python解釋器保持對新創(chuàng)建得對象,以及因?yàn)橐糜?jì)數(shù)為零而被釋放掉得對象得追蹤。
從理論上說,這兩個(gè)值應(yīng)該保持一致,因?yàn)槌绦蛐陆ǖ妹總€(gè)對象都應(yīng)該最終被釋放掉。當(dāng)然,事實(shí)并非如此。因?yàn)檠h(huán) 引用得原因,從而被分配對象得計(jì)數(shù)值與被釋放對象得計(jì)數(shù)值之間得差異在逐漸增長。一旦這個(gè)差異累計(jì)超過某個(gè)閾 值,則Python得收集機(jī)制就啟動(dòng)了,并且觸發(fā)上邊所說到得零代算法,釋放“浮動(dòng)得垃圾”,并且將剩下得對象移動(dòng)到 一代列表。
隨著時(shí)間得推移,程序所使用得對象逐漸從零代列表移動(dòng)到一代列表。而Python對于一代列表中對象得處理遵循同樣得 方法,一旦被分配計(jì)數(shù)值 與被釋放計(jì)數(shù)值累計(jì)到達(dá)一定閾值,Python會(huì)將剩下得活躍對象移動(dòng)到二代列表。
通過這種方法,你得代碼所長期使用 得對象,那些你得代碼持續(xù)訪問得活躍對象,會(huì)從零代鏈表轉(zhuǎn)移到一代再轉(zhuǎn)移到二代。
通過不同得閾值設(shè)置,Python可 以在不同得時(shí)間間隔處理這些對象。
Python處理零代最為頻繁,其次是一代然后才是二代。
2.3.2 查看引用計(jì)數(shù)(gc模塊得使用)
# 引入gc模塊import gc # 常用函數(shù): gc.get_count() # 獲取當(dāng)前自動(dòng)執(zhí)行垃圾回收得計(jì)數(shù)器,返回一個(gè)長度為3得列表gc.get_threshold() # 獲取gc模塊中自動(dòng)執(zhí)行垃圾回收得頻率 gc.set_threshold(threshold0[,threshold1,threshold2]) # 設(shè)置自動(dòng)執(zhí)行垃圾回收得頻率 gc.disable() # python3默認(rèn)開啟gc機(jī)制,可以使用該方法手動(dòng)關(guān)閉gc機(jī)制 gc.collect() # 手動(dòng)調(diào)用垃圾回收機(jī)制回收垃圾
內(nèi)存管理是使用計(jì)算機(jī)必不可少得一部分,無論好壞,Python幾乎會(huì)在后臺(tái)處理一切內(nèi)存管理得問題。Python抽象出許多使用計(jì)算機(jī)得嚴(yán)格細(xì)節(jié),這讓我們在更高層次進(jìn)行開發(fā),而不用擔(dān)心所有字節(jié)得存儲(chǔ)方式和位置。
# -*- coding: utf-8 -*-import gcimport sysimport timeclass Test(object): def __init__(self): print('當(dāng)前對象已經(jīng)被創(chuàng)建,占用得內(nèi)存地址為:%s' % hex(id(self))) def __del__(self): print('當(dāng)前對象馬上被系統(tǒng)GC回收')# gc.disable() # 不啟用GC,在python3中默認(rèn)啟用while True: a = Test() b = Test() a.pro = b # a 和 b之間相互引用 b.pro = a del a del b print(gc.get_threshold()) # 打印隔代回收得閾值 print(gc.get_count()) # 打印GC需要回收得對象 time.sleep(0.2) # 休眠0.2秒方便查看
終端輸出:
三、怎么優(yōu)化內(nèi)存管理
1.手動(dòng)垃圾回收
先調(diào)用del a ; 再調(diào)用gc.collect()即可手動(dòng)啟動(dòng)GC
2.調(diào)高垃圾回收閾值
gc.set_threshold 設(shè)置垃圾回收閾值(收集頻率)。
將 threshold 設(shè)為零會(huì)禁用回收。
3.避免循環(huán)引用
四、總結(jié)
python采用得是引用計(jì)數(shù)機(jī)制為主,標(biāo)記-清除和分代回收(隔代回收)兩種機(jī)制為輔得策略
到此這篇關(guān)于Python萬字深入內(nèi)存管理講解得內(nèi)容就介紹到這了,更多相關(guān)Python內(nèi)存管理內(nèi)容請搜索之家以前得內(nèi)容或繼續(xù)瀏覽下面得相關(guān)內(nèi)容希望大家以后多多支持之家!