1. 程式人生 > 實用技巧 >Python入門系列之GC機制

Python入門系列之GC機制

Python中的GC演算法

  • 分為一下三點:

    • 引用計數
    • 標記-清除
    • 分代回收
  • 簡述:

    • Python中的GC模組主要運用了引用計數來追蹤和回收垃圾.在引用計數的基礎上,還可以通過"標記-清除"解決容器物件可能產生的迴圈引用的問題.通過分代回收以空間換取時間進一步提交垃圾回收的效率
    • 標記-清除:
      • 標記-清除的出現打破了迴圈引用,也就是它只關注那些可能會產生迴圈引用的物件.
      • 缺點:該機制所帶來的額外操作和需要回收的記憶體成正比.
    • 分代回收:
      • 將系統中的所有記憶體根據其存活時間劃分為不同的集合,每一個集合成為一個"代",垃圾收集的頻率隨著'代'的存活時間的增大而減小,也就是說,活得越長的物件,就越不可能是垃圾,就應該減少對它的垃圾收集頻率.
      • 那麼如何衡量這個存活時間:通常是利用幾次垃圾收集動作來衡量,如果是一個物件經過的垃圾手機次數越多,可以得出:該物件存活時間就越長.
  • 下面對於三種回收機制進行具體解釋:

    • 引用計數(主要)

      • 在Python中萬物皆物件.在Python中每一個物件的核心就是一個結構體PyObject,它的內部有一個引用計數器(ob_refcnt)

      • 引用計數的意思就是,一個物件在被New方法創建出來的時候因為被New方法引用,所以他的引用計數就是1.如果他被其他物件引用(例如b=a,被丟入函式列表等待就會在引用計數上加1),如果引用它的物件被刪除(在之前的基礎上DEL b)那麼它的引用計數就會減少,知道引用計數變為0,垃圾回收機制就會將它回收.

      • 優/缺點:

        • 簡單,實時性
      • 缺點:

        • 維護性高(簡單實時,但額外佔用了一部分資源,雖然邏輯簡單,但是麻煩)

        • 不能解決的問題:迴圈引用

          a=[1,2]
          b=[2,3]
          a.append(b)
          b.append(a)
          DEL a
          DEL b
          
        • 說實話感覺有點像死鎖問題,這種問題出現在可以迴圈的結構中LIst,Dict,Object等待,如果程式碼a,b之間的引用都為1,而a,b被引用的物件刪除後各自減去1(所以他們各自的引用計數還是1)這種情況就無法解決了,也就為我們引入了下面的主題:標記-清除

      • 標記-清除

        • 標記清除就是用來解決迴圈引用的問題的,只有容器物件才會出現引用迴圈,比如列表,類,字典,元組.首先,為了追蹤容器物件,需要每個容器物件維護兩個額外的指標,用來將而容器物件組成一個連結串列,指標分別指向前後兩個容器物件,方便插入和刪除操作.

        • 例如,現有兩種情況

          A:
              a = [1,3]
              b = [2,4]
              a.append(b)
              b.append(a)
              del a
              del b
          

          B:
          a =[1,3]
          b = [2,4]
          a.append[b]
          b.append(a)
          del a

        • Okey,現在開始說正題.在標記清除演算法中,有兩個集中營,一個是root連結串列,另一個是unreachable連結串列

          • 對於情景A,原來在未執行DEL語句的時候,a,b的引用計數都為2(init+append=2),但是DEL執行完畢後,a,b引用次數互相減少1.a,b陷入迴圈引用的圈子,然後標記-清除演算法開始出來搞事了,找到其中一段a,開始拆這個a,b的引用環(我們從A出發,因為它有一個對B的引用,則將B的引用計數減1;然後順著引用到達B,因為B有一個對A的引用,同樣將A的引用減1,這樣就完成了迴圈引用物件之間環的摘除),去掉以後發現a,b迴圈引用變為了0,所以a,b就被處理到unreachable連結串列中被做掉了.
          • 對於情景B,簡單一看b去環之後引用計數還為1,但是a取環,就為0了,這時候a已經進入unreachable連結串列中,已經被判了死刑,但是這個時候,root連結串列中有b,在root連結串列中b會被引用檢測到引用了a,如果a被回收,b就涼涼了,所以a被拉回到root連結串列中
        • 為什麼要搞這兩個連結串列?

          • 之所以要剖成兩個連結串列,是基於這樣的一種考慮:現在unreachable可能存在被root連結串列中的物件,直接或間接引用的物件,這些物件不能被回收,一旦在標記的過程中,發現這樣的物件,就將其從unreachable連結串列中移到root連結串列中,當完成標記後,unreachable連結串列中剩下的物件就是名副其實的垃圾物件了,接下來的垃圾回收只需要限制在unreachable連結串列中即可.
      • 分代回收

        • 瞭解分代回收,首先要了解一下GC的閾值,所謂閾值就是一個臨界點的值,隨著你的程式的執行,Python直譯器保持對新建立物件,以及因為引用計數為零而被釋放掉的物件的追蹤.從理論上來說,建立==釋放的數量.但是如果存在引用迴圈,肯定會導致建立>釋放數量,當建立與釋放數量的差值到達規定的閾值的時候,分代回收機制就登場了.
        • 分代回收思想將物件分為三代(generation 0,1,2),0代表幼年物件,1代表青年物件,2代表老年物件.根據弱代假說(越年輕的物件越容易死掉,老的物件通常會存活更久),新生的物件被放入0代,如果該物件在第0帶的一次gc中活了下來,那麼它就被放到第1帶裡裡面(它就生就了).如果在第一代的一次gc垃圾回收中活了下來,他就被放到第2代裡面gc.set_threshold(threshold0[,threshold1[,threshold2]])設定gc每一代垃圾回收所出發的閾值.從上一代第0代gc後,如果分配物件的個數減去釋放的個數大於threshold0,那麼就會對第0代中的物件進行gc垃圾回收檢查.從上一次第1代gc後,如果第0代被gc垃圾回收的次數大於threshold1,那麼就會對第1代的物件進行gc垃圾回收檢查,從上一次第2代gc後,如果第一代被gc垃圾回收次數大於threshold2,那麼就會對第2代中的物件進行gc垃圾回收檢查

本文轉自https://www.cnblogs.com/Yongzyw/p/11520483.html