Python萬字深入內(nèi)存管理講解
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 b True >>>
我們可以看出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 b False >>>
a , b 不是指向同一個(gè)內(nèi)存地址。
python中對大于256的整數(shù),會(huì)重新分配對象空間地址保存對象。
3.inter機(jī)制(短字符串池)
每個(gè)單詞(字符串),不夾雜空格或者其他符號,默認(rèn)開啟intern機(jī)制,共享內(nèi)存,靠引用計(jì)數(shù)決定是否銷毀。
>>> s1 = 'hello' >>> s2 = 'hello' >>> id(s1) 2178093449264 >>> id(s2) 2178093449264 >>> s1 is s2 True >>>
字符串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 s2 False >>>
這時(shí)的字符串s1和s2就沒有指向同一個(gè)內(nèi)存地址。
對于字符串來說,如果不包含空格的字符串,則不會(huì)重新分配對象空間,對于包含空格的字符串則會(huì)重新分配對象空間。
二、垃圾回收
概述:
python采用的是引用計(jì)數(shù)機(jī)制為主,隔代回收和標(biāo)記清除機(jī)制為輔的策略
概述:
現(xiàn)在的高級語言如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 sys class 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)) # 2 b = a print('當(dāng)前對象的引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 3 list1 = [] list1.append(a) print('當(dāng)前對象的引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 4 del b print('當(dāng)前對象的引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 3 list1.remove(a) print('當(dāng)前對象的引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 2 del a print('當(dāng)前對象的引用計(jì)數(shù)為:%s' % sys.getrefcount(a)) # 報(bào)錯(cuò) ''' Traceback (most recent call last): File "E:/Python Project/Python 高級編程/內(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級,以及降低掃描的工作量,提高效率。
- 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ì)在后臺處理一切內(nèi)存管理的問題。Python抽象出許多使用計(jì)算機(jī)的嚴(yán)格細(xì)節(jié),這讓我們在更高層次進(jìn)行開發(fā),而不用擔(dān)心所有字節(jié)的存儲(chǔ)方式和位置。
# -*- coding: utf-8 -*- import gc import sys import time class 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)存管理講解的文章就介紹到這了,更多相關(guān)Python內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用OpenCV進(jìn)行年齡和性別檢測的實(shí)現(xiàn)示例
這篇文章主要介紹了用 OpenCV 進(jìn)行年齡和性別檢測的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01OpenCV根據(jù)面積篩選連通域?qū)W習(xí)示例
這篇文章主要為大家介紹了OpenCV根據(jù)面積篩選連通域?qū)W習(xí)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06我在七夕佳節(jié)用Python制作的表白神器,程序員也應(yīng)該擁有愛情!建議收藏
這篇文章主要介紹了我在七夕佳節(jié)用Python制作的表白神器,建議收藏,程序員也該擁有愛情,感興趣的小伙伴快來看看吧2021-08-08Python批量實(shí)現(xiàn)Word/EXCEL/PPT轉(zhuǎn)PDF
在日常辦公和文檔處理中,有時(shí)我們需要將多個(gè)Word文檔、Excel表格或PPT演示文稿轉(zhuǎn)換為PDF文件,本文將介紹如何使用Python編程語言批量實(shí)現(xiàn)將多個(gè)Word、Excel和PPT文件轉(zhuǎn)換為PDF文件,需要的可以參考下2023-09-09