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 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)容希望大家以后多多支持之家!

    聲明:所有內(nèi)容來自互聯(lián)網(wǎng)搜索結(jié)果,不保證100%準(zhǔn)確性,僅供參考。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系我們進(jìn)行處理。
    發(fā)表評(píng)論
    更多 網(wǎng)友評(píng)論1 條評(píng)論)
    暫無評(píng)論

    返回頂部

    主站蜘蛛池模板: 一区国产传媒国产精品| 久久精品黄AA片一区二区三区| 国产福利微拍精品一区二区| 国产伦精品一区三区视频| 亚洲综合无码AV一区二区| 狠狠色婷婷久久一区二区三区| 日韩在线视频不卡一区二区三区| 国产精品无码AV一区二区三区| 韩国福利视频一区二区| 精品国产一区二区三区香蕉| 国产suv精品一区二区33| 果冻传媒一区二区天美传媒| 国产亚洲综合精品一区二区三区| 亚洲一区二区影视| 亚洲一区动漫卡通在线播放| 无码一区二区三区免费| 国产一区二区精品尤物| 久久久精品日本一区二区三区| 日韩美女视频一区| 国产激情一区二区三区四区| 日本一区二区三区不卡在线视频| 国产乱码精品一区二区三区麻豆 | 伊人久久精品无码麻豆一区| 精品国产一区二区三区久久久狼| 亚洲一区二区三区免费在线观看 | 成人精品视频一区二区三区| 波霸影院一区二区| 国产精品第一区第27页| 中文字幕人妻无码一区二区三区 | 亚洲熟妇AV一区二区三区浪潮| 亚洲区精品久久一区二区三区| 久久精品免费一区二区| 亚洲日韩国产一区二区三区在线 | 日本一区二区三区在线观看视频| 日韩精品一区二区三区在线观看l| 日韩AV片无码一区二区不卡| 日韩动漫av在线播放一区| 中文字幕久久久久一区| 人妻夜夜爽天天爽爽一区| 丰满爆乳无码一区二区三区| 国产情侣一区二区三区|