記憶體管理機制
記憶體的管理:配置設定(malloc)+回收(free)
作用:控制python記憶體,對python記憶體進行回收
- python中一切皆對象,python的存儲就是配置設定記憶體空間去存儲對象
- 整數和短小的字元(基本就是一個單詞)使用的是緩存機制,以便快速重複使用
- 使用is檢驗是否為同一個對象
三個方面:引用計數+垃圾回收+記憶體池
引用計數機制
引用計數機制:⭐️
- 記錄對象被引用的次數
- 優點:
- 簡單
- 實時性:對象一旦沒有引用,記憶體直接釋放
- 缺點:
- 維護引用計數消耗資源
- 解決不了循環引用問題
- 什麼時候引用計數增加?
1 對象建立a=1 2 對象引用b=a 3 對象作為參數傳遞func(a) 4 對象存儲在容器中1=[a]
- 什麼時候引用計數減少?
1 顯示使用del a 2 引用指向了别的對象b=None 3 離開了對象的作用域(比如函數執行結束) 4 從一個容器移除對象或者銷毀容器
- 檢視引用計數
1 import sys 2 a = [1,2] 3 sys.getrefcount(a) #檢視引用計數,getrefcount 本身也會引入一次計數 4 #傳遞給getrefcount()時,參數實際上建立了一個臨時的引用。是以,getrefcount()所得到的結果,會比期望的多1。
垃圾回收機制(GC:Garbage Collection)
作用:在python解釋器中,會自動回收沒有用的記憶體
功能:
- 為新對象配置設定記憶體
- 識别垃圾對象
- 從垃圾對象那回收記憶體
實作: 引用計數為主 + 标記清除和分代回收為輔
- 引用計數為0時自動回收
- 标記清除算法:⭐️
- 用圖論來了解不可達的概念。對于一個有向圖,如果從一個節點出發進行周遊,并标記其經過的所有節點;在周遊結束後,所有沒有被标記的節點,稱之為不可達節點。不可達節點的存在是沒有任何意義的,需要對它們進行垃圾回收。
- 每次都周遊全圖,一種巨大的性能浪費。是以,标記清除算法使用雙向連結清單維護了一個資料結構,并且隻考慮容器類的對象(隻有容器類對象才有可能産生循環引用)。
- 分代收集算法:⭐️
- 将 Python 中的所有對象分為三代。剛創立的對象是第 0 代;經過一次垃圾回收後,依然存在的對象,便會依次從上一代挪到下一代。而每一代啟動自動垃圾回收的門檻值,則是可以單獨指定的。當垃圾回收器中新增對象減去删除對象達到相應的門檻值時,就會對這一代對象啟動垃圾回收。
- 觸發垃圾回收機制的情況
- gc.collect():主動回收
- gc子產品的計數器達到3個門檻值時
- 程式退出時
1 # 手動釋放記憶體 2 import gc 3 del a 4 gc.collect() # 主動回收
- 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為界限)
- 大記憶體使用malloc進行配置設定
- 小記憶體使用記憶體池進行配置設定
記憶體池
當建立大量消耗小記憶體的對象時,頻繁調用new/malloc會導緻大量的記憶體碎片,緻使效率降低。
記憶體池的概念就是預先在記憶體中申請一定數量的,大小相等的記憶體塊留作備用,當有新的記憶體需求時,就先從記憶體池中配置設定記憶體給這個需求,不夠了之後再申請新的記憶體。這樣做最顯著的優勢就是能夠減少記憶體碎片,提升效率
第3層:最上層,使用者對Python對象的直接操作
第1層和第2層:記憶體池,有Python的接口函數PyMem_Malloc實作-----若請求配置設定的記憶體在1~256位元組之間就使用記憶體池管理系統進行配置設定,調用malloc函數配置設定記憶體,但是每次隻會配置設定一塊大小為256K的大塊記憶體,不會調用free函數釋放記憶體,将該記憶體塊留在記憶體池中以便下次使用。(記憶體小于256K)
第0層:大記憶體-----若請求配置設定的記憶體大于256K,malloc函數配置設定記憶體,free函數釋放記憶體。
第-1,-2層:作業系統進行操作
記憶體洩漏
- 原因:
- 所用到的用 C 語言開發的底層子產品中出現了記憶體洩露。
- 代碼中用到了全局的 list、 dict 或其它容器,不停的往這些容器中插入對象,而忘記了在使用完之後進行删除回收
- 代碼中有“引用循環”,并且被循環引用的對象定義了del方法,就會發生記憶體洩露。
- 診斷思路:python 對象在不停的增長;是以,首先是要找到這些異常的對象。
- 診斷步驟
- 工具:
- gc子產品
- objgraph子產品(第三方子產品,用于診斷記憶體問題)
- 在服務程式的循環邏輯中,選擇出一個診斷點
- 在診斷點插入如下語句
1 import gc 2 import objgraph 3 4 # 強制進行垃圾回收 5 gc.collect() 6 7 # 列印出對象數目最多的50個類型資訊 8 objgraph.show_most_common_types(limit=50)
- 工具:
記憶體溢出
-
- 記憶體中加載的資料量過于龐大
- 集合類中有對對象的引用,使用完後未清空,産生了堆積,使得JVM不能回收
- 代碼中存在死循環或循環産生過多重複的對象實體
- 使用的第三方軟體中的BUG
- 啟動參數記憶體值設定的過小
- 解決方案:
- 修改JVM啟動參數,直接增加記憶體(-Xms,-Xmx參數一定不要忘記加)
- 檢查錯誤日志,檢視“OutOfMemory”錯誤前是否有其 它異常或錯誤
-
對代碼進行走查和分析,找出可能發生記憶體溢出的位置
重點排查以下幾點:
- 檢查對資料庫查詢中,是否有一次獲得全部資料的查詢。一般來說,如果一次取十萬條記錄到記憶體,就可能引起記憶體溢出。這個問題比較隐蔽,在上線前,資料庫中資料較少,不容易出問題,上線後,資料庫中資料多了,一次查詢就有可能引起記憶體溢出。是以對于資料庫查詢盡量采用分頁的方式查詢。
- 檢查代碼中是否有死循環或遞歸調用。
- 檢查是否有大循環重複産生新對象實體。
- 檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。
- 使用記憶體檢視工具動态檢視記憶體使用情況
- 與記憶體洩漏的差別
- 記憶體溢出是指向JVM申請記憶體空間時沒有足夠的可用記憶體了,就會抛出OOM即記憶體溢出。