1. 程式人生 > >詳解python的垃圾回收機制

詳解python的垃圾回收機制

內存泄露 具體實現 劃分 物理 什麽 內部 容器 可用 回收機制

python的垃圾回收機制

一、引子

我們定義變量會申請內存空間來存放變量的值,而內存的容量是有限的,當一個變量值沒有用了(簡稱垃圾)就應該將其占用的內存空間給回收掉,而變量名是訪問到變量值的唯一方式,所以當一個變量值沒有關聯任何變量名時,我們就無法再訪問到該變量值了,該變量值就是一個垃圾會被python解釋的垃圾回收機制自動回收

二、什麽是垃圾回收機制

垃圾回收機制(簡稱GC)是python解釋器自帶的一種機制,專門用來回收不可用的變量值所占用的內存空間

三、為什麽要用垃圾回收機制

程序運行過程中會申請大量的內存空間,為對於一些無用的內存空間如果不及時清理的話會導致內存使用殆盡(內存的益出),導致程序的崩潰,因此管理內存是一件非常重要而繁雜的事情,而python解釋器自帶的垃圾回收機制把程序員從繁雜的內存管理中解放出來

四、垃圾回收機制原理分析

python的GC模塊主要運用了“引用計數”(reference counting)來跟蹤和回收垃圾,在引用計數的基礎上,還可以通過“標記-清楚”(Mark and sweep)解決容器對象可能產生的循環引用問題,並且通過“分代回收”(generation collection)以空間換取時間的方式來進一步提高垃圾回收的效率

五、什麽是引用計數

引用計數就是:變量值被變量名關聯的次數

如:name=“maple”

變量值maple被關聯了一個name,稱之為引用計數為1,

引用計數增加:

x=10(此時,變量值10的引用計數為1)

y=10(此時,把x的內存地址給了y,此時,x,y都關聯了10,所以變量值10的引用計數為2)

引用計數減少:

x=3(此時,x與10解除了關聯,與3建立了關聯,變量10的引用計數為1)

del y (del的意思是解除變量名y與變量值10的關聯關系,此時,變量10的引用計數為0)

這樣變量值10的引用計數為0,其占用的內存地址就會被回收

六、引用計數擴展

引用計數機制執行效率問題:變量值被關聯次數的增加或減少,都會引發引用計數機制的執行,這存在明顯的效率問題

如果說執行效率還僅僅是引用計數機制的一個軟肋的話,那麽很不幸,引用計數機制還存在著一個致命的弱點,即循環引用(也成交叉引用)

# 變量名l1指向列表1,變量名l2指向列表2,如下
>>> l1=[列表1中的第一個元素]  # 列表1被引用一次   
>>> l2=[列表2中的第一個元素]  # 列表2被引用一次 
>>> l1.append(l2)             # 把列表2追加到l1中作為第二個元素,列表2的引用計數為2
>>> l2.append(l1)             # 把列表1追加到l2中作為第二個元素,列表1的引用計數為2
# l1與l2
# l1 = [‘列表1中的第一個元素‘,列表2的內存地址]
# l2 = [‘列表2中的第一個元素‘,列表1的內存地址]

循環引用可以使一組對象的引用計數不為0,然而這些對象實際上並沒有被任何外部對象所引用,它們之間只是相互引用,這意味著不會再有人使用這組對象,應該回收這組對象所占用的內存空間,然後由於相互引用的存在,每一個對象的引用計數都不為0,因此這些對象所占用的內存元永遠不會被釋放,比如:

>>> l1
[列表1中的第一個元素, [列表2中的第一個元素, [...]]]
>>> l2
[列表2中的第一個元素, [列表1中的第一個元素, [...]]]
>>> l1[1][1][0]
列表1中的第一個元素

如果我們執行del l1,列表1的引用計數=2-1,即列表1不會被回收,同理del l2,列表2的引用計數=2-1,此時無論列表1還是列表2都沒有任何名字關聯,但是引用計數均不為0,所以循環引用是致命的,這與手動進行內存管理所產生的內存泄露毫無區別

要解決這個問題,python引入了其他的垃圾回收機制來彌補引用計數的缺陷:

1、“標記-清楚”

2、“分代回收”

標記-清楚

容器對象(比如:list,set,dict,class,instance)都可以包含對其他對象的引用,所以都可能產生循環引用,而“標記-清楚”計數就是為了解決循環引用的問題

在了解標記清楚算法前,我們需要明確一點,內存中有兩塊區域:堆曲與棧曲,在定義變量時,變量名存放於棧區,變量值存放於堆區,內存管理回收的則是堆區的內容

標記清楚算法的做法是當有效內存空間被消耗盡的時候,就會停止整個程序,然後進行兩項工作,第一項則是標記,第二項則是清楚

標記:標記的過程其實就是,遍歷所有的GC roots對象(棧區中的所有內容或者線程都可以作為GC roots對象),然後將所有GC roots的對象可以直接或間接訪問的對象標記為存活的對象

清楚:清楚的過程將遍歷堆區中所有的對象,將沒有標記的對象全部清楚掉

GC roots對象直接訪問到的對象

GC roots對象間接訪問到的對象

分代回收

背景:

基於引用計數的回收機制,每次回收內存,都需要把所有對象的引用計數都遍歷一遍,這是非常消耗時間的,於是引入了分代回收來提高回收效率,采用“空間換時間的策略”。

什麽是分代回收?

分代:

分代回收的核心思想是:在多次掃描的情況下,都沒有被回收的變量,GC機制就會認為,該變量是常用變量,GC對其掃描的頻率會降低,具體實現原理如下:

分代指的是根據存活時間來為變量劃分不同等級(也就是不同的代)

新定義的變量,放到新生代這個等級中,假設每隔1分鐘掃描新生代一次,如果發現變量依然被引用,那麽該對象的權重(權重本質就是個整數)加一,當變量的權重大於某個設定得值(假設為3),會將它移動到更高一級的青春代,青春代的gc掃描的頻率低於新生代(掃描時間間隔更長),假設5分鐘掃描青春代一次,這樣每次GC需要掃描的變量的總個數就變少了,節省了掃描的總時間,接下來,青春代中的對象,也會以同樣的方式被移動到老年代中。也就是等級(代)越高,被垃圾回收機制掃描的頻率越低

回收:

回收依然是使用引用計數作為回收的依據

缺點:

例如一個變量剛剛從新生代移入青春代,該變量的綁定關系就解除了,該變量應該被回收,青春代的掃描頻率低於新生代,所以該變量的回收時間被延遲

內存池

1. Python 的內存機制呈現金字塔形狀,-1,-2 層主要有操作系統進行操作;
2. 第 0 層是 C 中的 malloc,free 等內存分配和釋放函數進行操作;
3. 第1 層和第 2 層是內存池,有 Python 的接口函數 PyMem_Malloc 函數實現,當對象小於256K 時有該層直接分配內存;
4. 第3層是最上層,也就是我們對 Python 對象的直接操作;
Python 在運行期間會大量地執行 malloc 和 free 的操作,頻繁地在用戶態和核心態之間進行切 換,這將嚴重影響 Python 的執行效率。為
了加速Python 的執行效率,Python 引入了一個內存池 機制,用於管理對小塊內存的申請和釋放。 Python 內部默認的小塊內存與大塊內存
的分界點定在 256 個字節,當申請的內存小於 256 字節 時,PyObject_Malloc會在內存池中申請內存;當申請的內存大於 256 字節時,
PyObject_Malloc 的 行為將蛻化為 malloc 的行為。當然,通過修改 Python 源代碼,我們可以改變這個默認值,從而改 變 Python 的默認
內存管理行為。

調優手段

1.手動垃圾回收
2.調高垃圾回收閾值
3.避免循環引用(手動解循環引用和使用弱引用)
2.內存泄露是什麽?如何避免?
指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏並非指內存在物理上的
消失,而是應用程序分配某段內存後,由於設計錯誤,失去了對該段內存的控制,因而造成了內存的浪
費。導致程序運行速度減慢甚至系統崩潰等嚴重後果。
有 __del__() 函數的對象間的循環引用是導致內存泄漏的主兇。
不使用一個對象時使用:del object 來刪除一個對象的引用計數就可以有效防止內存泄漏問題。
通過Python 擴展模塊 gc 來查看不能回收的對象的詳細信息。
可以通過 sys.getrefcount(obj) 來獲取對象的引用計數,並根據返回值是否為 0 來判斷是否內存
泄漏。

詳解python的垃圾回收機制