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