天天看點

python的記憶體管理機制

記憶體管理機制

記憶體的管理:配置設定(malloc)+回收(free)

作用:控制python記憶體,對python記憶體進行回收

  1. python中一切皆對象,python的存儲就是配置設定記憶體空間去存儲對象
  2. 整數和短小的字元(基本就是一個單詞)使用的是緩存機制,以便快速重複使用
  3. 使用is檢驗是否為同一個對象

三個方面:引用計數+垃圾回收+記憶體池

引用計數機制

引用計數機制:⭐️

  1. 記錄對象被引用的次數
  2. 優點:
    1. 簡單
    2. 實時性:對象一旦沒有引用,記憶體直接釋放
  3. 缺點:
    1. 維護引用計數消耗資源
    2. 解決不了循環引用問題
  4. 什麼時候引用計數增加?
    1 對象建立a=1
    2 對象引用b=a
    3 對象作為參數傳遞func(a)
    4 對象存儲在容器中1=[a]      
  5. 什麼時候引用計數減少?
    1 顯示使用del a
    2 引用指向了别的對象b=None
    3 離開了對象的作用域(比如函數執行結束)
    4 從一個容器移除對象或者銷毀容器      
  6. 檢視引用計數
    1 import sys
    2 a = [1,2]
    3 sys.getrefcount(a)      #檢視引用計數,getrefcount 本身也會引入一次計數
    4 #傳遞給getrefcount()時,參數實際上建立了一個臨時的引用。是以,getrefcount()所得到的結果,會比期望的多1。      

垃圾回收機制(GC:Garbage Collection)

作用:在python解釋器中,會自動回收沒有用的記憶體

功能:

  1. 為新對象配置設定記憶體
  2. 識别垃圾對象
  3. 從垃圾對象那回收記憶體

實作: 引用計數為主 + 标記清除和分代回收為輔

  1. 引用計數為0時自動回收
  2. 标記清除算法:⭐️
    1. 用圖論來了解不可達的概念。對于一個有向圖,如果從一個節點出發進行周遊,并标記其經過的所有節點;在周遊結束後,所有沒有被标記的節點,稱之為不可達節點。不可達節點的存在是沒有任何意義的,需要對它們進行垃圾回收。
    2. 每次都周遊全圖,一種巨大的性能浪費。是以,标記清除算法使用雙向連結清單維護了一個資料結構,并且隻考慮容器類的對象(隻有容器類對象才有可能産生循環引用)。
  3. 分代收集算法:⭐️
    1. 将 Python 中的所有對象分為三代。剛創立的對象是第 0 代;經過一次垃圾回收後,依然存在的對象,便會依次從上一代挪到下一代。而每一代啟動自動垃圾回收的門檻值,則是可以單獨指定的。當垃圾回收器中新增對象減去删除對象達到相應的門檻值時,就會對這一代對象啟動垃圾回收。
  4. 觸發垃圾回收機制的情況
    1. gc.collect():主動回收
    2. gc子產品的計數器達到3個門檻值時
    3. 程式退出時
    1 # 手動釋放記憶體
    2 import gc
    3 del a
    4 gc.collect()    # 主動回收      
  5. GC中的方法
    1 gc.garbage  # 垃圾回收後的對象會放在這裡
    2 gc.get_count()  # 傳回長度為3的清單,分别表示第0代對象,第1代對象和第2代對象 值
    3 gc.get_threshold()  # 傳回長度為3的清單,分别表示第0代對象,第1代對象和第2代對象的 閥值,通常時(700,10,10)
    4 # 每10次0代垃圾回收,會配合1次1代的垃圾回收;而每10次1代的垃圾回收,才會有1次的2代垃圾回收。
    5 gc.set_threshold()  # 設定第0代、第1代、第2代對象的 門檻值 比如:gc.set_threshold(700,10,5)
    6 gc.collect([第幾代]) # 也可以輸入0,1,2,0:隻檢查第一代,1:隻檢查第一代和第二代,2:檢查3代
    7 gc.disable      

記憶體池機制(Pymalloc)

python中有大記憶體和小記憶體之分(以256KB為界限)

  1. 大記憶體使用malloc進行配置設定
  2. 小記憶體使用記憶體池進行配置設定

記憶體池

當建立大量消耗小記憶體的對象時,頻繁調用new/malloc會導緻大量的記憶體碎片,緻使效率降低。

記憶體池的概念就是預先在記憶體中申請一定數量的,大小相等的記憶體塊留作備用,當有新的記憶體需求時,就先從記憶體池中配置設定記憶體給這個需求,不夠了之後再申請新的記憶體。這樣做最顯著的優勢就是能夠減少記憶體碎片,提升效率

第3層:最上層,使用者對Python對象的直接操作

第1層和第2層:記憶體池,有Python的接口函數PyMem_Malloc實作-----若請求配置設定的記憶體在1~256位元組之間就使用記憶體池管理系統進行配置設定,調用malloc函數配置設定記憶體,但是每次隻會配置設定一塊大小為256K的大塊記憶體,不會調用free函數釋放記憶體,将該記憶體塊留在記憶體池中以便下次使用。(記憶體小于256K)

第0層:大記憶體-----若請求配置設定的記憶體大于256K,malloc函數配置設定記憶體,free函數釋放記憶體。

第-1,-2層:作業系統進行操作

記憶體洩漏

  1. 原因:
    1. 所用到的用 C 語言開發的底層子產品中出現了記憶體洩露。
    2. 代碼中用到了全局的 list、 dict 或其它容器,不停的往這些容器中插入對象,而忘記了在使用完之後進行删除回收
    3. 代碼中有“引用循環”,并且被循環引用的對象定義了del方法,就會發生記憶體洩露。
  2. 診斷思路:python 對象在不停的增長;是以,首先是要找到這些異常的對象。
  3. 診斷步驟
    1. 工具:
      1. gc子產品
      2. objgraph子產品(第三方子產品,用于診斷記憶體問題)
    2. 在服務程式的循環邏輯中,選擇出一個診斷點
    3. 在診斷點插入如下語句
      1 import gc
      2 import objgraph
      3 ​
      4 # 強制進行垃圾回收
      5 gc.collect()
      6 ​
      7 # 列印出對象數目最多的50個類型資訊
      8 objgraph.show_most_common_types(limit=50)      

記憶體溢出

    1. 記憶體中加載的資料量過于龐大
    2. 集合類中有對對象的引用,使用完後未清空,産生了堆積,使得JVM不能回收
    3. 代碼中存在死循環或循環産生過多重複的對象實體
    4. 使用的第三方軟體中的BUG
    5. 啟動參數記憶體值設定的過小
  1. 解決方案:
    1. 修改JVM啟動參數,直接增加記憶體(-Xms,-Xmx參數一定不要忘記加)
    2. 檢查錯誤日志,檢視“OutOfMemory”錯誤前是否有其 它異常或錯誤
    3. 對代碼進行走查和分析,找出可能發生記憶體溢出的位置

      重點排查以下幾點:

      1. 檢查對資料庫查詢中,是否有一次獲得全部資料的查詢。一般來說,如果一次取十萬條記錄到記憶體,就可能引起記憶體溢出。這個問題比較隐蔽,在上線前,資料庫中資料較少,不容易出問題,上線後,資料庫中資料多了,一次查詢就有可能引起記憶體溢出。是以對于資料庫查詢盡量采用分頁的方式查詢。
      2. 檢查代碼中是否有死循環或遞歸調用。
      3. 檢查是否有大循環重複産生新對象實體。
      4. 檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。
    4. 使用記憶體檢視工具動态檢視記憶體使用情況
  2. 與記憶體洩漏的差別
  3. 記憶體溢出是指向JVM申請記憶體空間時沒有足夠的可用記憶體了,就會抛出OOM即記憶體溢出。