1. 程式人生 > 程式設計 >python垃圾回收機制(GC)原理解析

python垃圾回收機制(GC)原理解析

這篇文章主要介紹了python垃圾回收機制(GC)原理解析,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

  今天想跟大家分享的是關於python的垃圾回收機制,雖然本人這會對該機制沒有很深入的瞭解,但是本著熱愛分享的原則,還是囫圇吞棗地坐下記錄分享吧,萬一分享的過程中開竅了呢.哈哈哈.

  首先還是做一下概述吧: 我們都知道,在做python的語言程式設計中,相較於java,c++,我們似乎很少去考慮到去做垃圾回收,記憶體釋放的工作,其實是python內部已經做了相應的回收機制,不用我們自己操心去做記憶體釋放.但是還是有必要了解一下.可以更加深入的瞭解python這門優美的語言的魅力.

一、概述:

  python的GC模組主要運用了“引用計數(reference counting)”來跟蹤和回收垃圾。在引用計數的基礎上,還可以通過標記清除(mark and sweep)解決容器(這裡的容器值指的不是docker,而是陣列,字典,元組這樣的物件)物件可能產生的迴圈引用的問題。通過“分代回收(generation collection)”以空間換取時間來進一步提高垃圾回收的效率。

二、垃圾回收三種機制

  1、引用計數

  在Python中,大多數物件的生命週期都是通過物件的引用計數來管理的, 廣義上講,它也是一種垃圾回收機制,而且是一種最直觀最簡單的垃圾回收機制。

  原理:當一個物件被建立引用或者被複制的時候,物件的引用計數會加一,當一個物件的引用被銷燬時,物件的引用計數會減一,當物件的引用計數減為0的時候,就意味著物件已經沒有被任何人使用了,可以將其所佔用的記憶體釋放了。

  雖然引用計數必須在每次分配和釋放記憶體的時候加入管理引用計數的這個動作,然而與其他主流垃圾收集機制相比, 最大的一個優點是實時性, 及任何記憶體,一旦沒有指向他的引用,就會立即被回收,其他的垃圾回收機制必須在某種特殊條件下(記憶體分配失敗)才能進行無效記憶體的回收。

  執行效率問題: 引用計數機制帶來的維護引用計數帶來的額外操作與python執行中所執行的記憶體分配和釋放,引用賦值的次數是成正比的。相比其他機制,比如“標記-清除”,“停止-複製”,是一個弱點,因為這些技術所帶來的操作基本上只是與待回收的數量有關。

  引用計數還存在的一個致命的弱點是迴圈引用,這使得垃圾回收機制從來沒有將引用計數包含在內。這就需要我們用新的方法了, 即標記清除。

  2、標記清除

標記清除主要是用來解決迴圈引用產生的問題的,迴圈引用只會在容器物件中才會產生,比如陣列、字典、元組等,首先是為了追蹤物件,需要每個容器物件維護兩個額外的指標,用來將容器物件組成一個連結串列,指標分別指向前後兩個容器物件,這樣就可以將物件的迴圈引用環摘除,就可以得出兩個物件的有效計數。

問題說明:

  迴圈引用可以使得一組物件的引用計數不是0, 然而這些物件實際上並沒有被外部物件所引用,這就意味著不會再有人使用這組物件, 應該回收這組物件所佔用的記憶體空間,然而由於相互引用的存在,每一個物件的引用計數不為0,因為這些物件所佔用的記憶體永遠不會被釋放。比如下面的程式碼:

a = [1,2]
b = [3,4]
a.append(b)
b.append(a)
del a
del b
# B
c = [3,5]
d = [2,4]
c.append(d)
d.append(c)
del c

OKAY,現在就這個做一下解釋,這是個集中營, 一個是root object(連結串列),另一個是unreachable連結串列。

對於上面的第一組, 在未執行del語句的時候,a,b的引用計數都是2(init + append= 2),但是在DEL執行完畢之後,a,b的引用次數互相減一。a,b陷入迴圈引用的圈子中,然後標記清除演算法開始出來做事,找到其中一端a,開始拆a,b的引用環(我們從a出發,因為它對B有一個引用,則將B的引用計數減一,然後順著引用到達B,因為B有一個對A的引用,同樣將A的引用減一,這樣就完成了迴圈引用物件之間的物件環摘除), 去掉以後發現a,c迴圈引用變成了0,所以a,b就被處理到unreachable連結串列中直接被做掉。

對於第二組,簡單一看d取環後引用計數還是1,但是a取環後就是0了這時的c已經進入了unreachable的連結串列中,被判了死刑,但是此時在root表中還有d,d還在引用著c,如果c被搞掉,世界就沒有了正義。root連結串列中的d會被引用檢測引用了c,如果c沒了,那麼b也就涼涼了,所以c又拉回到了root連結串列中。

解剖這兩個連結串列的原因是現在在unreachable中可能存在被root連結串列中的物件,直接或者間接引用的物件,這些物件是不能被回收的,一旦在標記的過程中,發現這樣的物件就將其移動到root連結串列中,完成標記後,unreachable連結串列中剩下的就是名副其實的垃圾物件了,接下來垃圾回收只需要限制在unreachable連結串列中即可。

  3、分代回收

背景:分代回收技術是上個世紀80年代初發展起來的一種垃圾回收機制,經過研究表明:無論使用何總語言開發無論開發的是何種型別,何種規模的程式,都存在這樣一點相同之處, 即:一定比例的記憶體塊的生存週期都比較短,通常是幾百萬條指令的時間,然而剩下的記憶體塊,生存週期比較長,甚至會從一開始直到程式結束。

從前面的“標記-清除”這樣的垃圾回收機制來看,這種垃圾收集機制帶來的額外操作實際上與系統中總的記憶體塊的數量是相關的,當要回收的記憶體塊越多時,垃圾檢測帶來的額外操作就越多,而垃圾回收所帶來的額外操作就越少,反值則相反。為了提高垃圾的收集效率,採用“空間換時間”的策略。

原理: 將系統紅所有記憶體塊根據其存活時間劃分為不同的集合每一個集合就稱為一個“代”,垃圾收集的頻率隨著代的存活時間的增大而減少。也即,活的時間越長的物件就越不可能是垃圾,就應該減少對它的垃圾收集頻率,衡量的標準就是這個物件經過的垃圾收集次數越多,該物件存活的時間就越長。

例如:

  當某些記憶體塊M經過了3次垃圾回收的清洗之後還是存活著的時候,就將記憶體塊M劃到一個集合A中去,當垃圾收集開始工作時,大多數情況只是針對集合B進行垃圾回收,而對集合A進行垃圾回收要隔相當長一段時間才進行,這就使得垃圾收集機制要處理的記憶體少了,效率自然就提高了。這個過程中集合B中的某些記憶體塊由於存活時間長會被轉移到A中, 當然A中實際上也存在一些垃圾,這些垃圾回收會因為這種分帶機制而延遲。 在python中,一共有三代,也即維護3條連結串列(generation 0, 1, 2)

  • 0代表幼兒物件。
  • 1代表青年物件。
  • 2代表老年物件。

依據弱代假說(越年輕的越容易死掉)

新生的物件放在0代,物件在0代的第一次垃圾收集機制中活了過來, 那麼久將其放到第1代裡面了,同理,可能會被放到第2代。GC每代垃圾回收處罰的閾值可以自己設定(目前我不知道怎麼設定/苦笑)。

這些就是目前的python的垃圾回收機制了。

下面的是 記憶體池以及調優手段:

記憶體池:

python的記憶體機制呈現金字塔形狀,-1, -2層主要由作業系統進行操作。

第0層是C中的malloc, free等記憶體分配和釋放函式進行操作

第一層和第二層是記憶體池,有python藉口函式,PyMem_Malloc函式實現,當物件小於256K時候由該層直接分配記憶體,

第三層是最上層,也即我們對python物件的直接操作。

調優手段:

1、手動垃圾回收

2、避免迴圈運用

3、提高垃圾回收閾值

希望上面這些能對大家有所幫助。謝謝支援!!!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。