在上一篇文章中(python 內存管理機制—引用計數)中,咱們介紹了python內存管理機制中的引用計數,python正是經過它來有效的管理內存。今天來介紹python的垃圾回收,其主要策略是引用計數爲主,標記-清除和分代回收爲輔助的策略(熟悉java的同窗回回憶下,其實這和JVM的策略是有相似之處的)。java
咱們還接着上一篇文章來接着介紹引用計數的相關場景,方便咱們來理解python如何經過引用計數來進行垃圾回收。其實經過字面意思,咱們應該也不難理解,當一個對象的引用計數變爲0時,表示沒有對象再使用這個對象,至關於這個對象變成了無用的"垃圾",當python解釋器掃描到這個對象時就能夠將其回收掉。python
咱們經過一些例子來看下,可使python對象的引用計數增長或減小的場景:面試
# coding=utf-8 """ ~~~~~~~~~~~~~~~~~ @Author:xuanke @contact: 784876810@qq.com @date: 2019-11-29 19:52 @function: 驗證引用計數增長和減小的場景 """ import sys def ref_method(str): print(sys.getrefcount(str)) print("我調用了{}".format(str)) print('方法執行完了') def ref_count(): # 引用計數增長的場景 print('測試引用計數增長') a = 'ABC' print(sys.getrefcount(a)) b = a print(sys.getrefcount(a)) ref_method(a) print(sys.getrefcount(a)) c = [1, a, 'abc'] print(sys.getrefcount(a)) # 引用計數減小的場景 print('測試引用計數減小') del b print(sys.getrefcount(a)) c.remove(a) print(sys.getrefcount(a)) del c print(sys.getrefcount(a)) a = 783 print(sys.getrefcount(a)) if __name__ == '__main__': ref_count()
運行結果以下:算法
測試引用計數增長 7 8 10 我調用了ABC 方法執行完了 8 9 測試引用計數減小 8 7 7 4
從上面的結果咱們得出如下結論:bash
引用計數雖然能夠實時的知道某個對象是否能夠被回收,可是也有兩個缺點:函數
爲了解決引用計數法沒法解決的循環引用問題,python採用了標記-回收垃圾回收算法,它的整個過程分爲兩步:性能
須要注意的是在python中能夠產生循環引用問題的多是:列表、字典、用戶自定義類的對象、元組等對象,而對於數字字符串這種簡單的數據類型,並不會產生循環引用,所以後者並不在標記清除算法的考慮之列。測試
針對標記-回收垃圾回收的過程,我從網上找了幾張圖片,方便你們來了解整個過程:優化
第一張圖是初始狀態,圖片上不只有ref_count,還有一個gc_ref的值,這個gc_ref其實就是爲了來解決引用計數問題的,它是ref_count的一個副本,因此它的初始值和ref_count保持一致。當開始遍歷全部對象時,當發現link1引用了link2對象時,會將link2的gc_ref值減小1,如此類推,就獲得下圖的結果。操作系統
第二張圖中咱們看到link二、link三、link4的gc_ref都已經爲0,當python垃圾回收器再次掃描全部對象時,那麼它們就會被標記爲GC_TENTATIVELY_UNREACHABLE,同時被移到Unreachable列表中。有同窗可能會疑惑爲啥link2沒有被移到Unreachable列表中,其實它理論上也應該被移到Unreachable列表中,如第三張圖所示:
若是python垃圾回收器再次掃描對象時,發現某個對象的ref_count不爲0,那麼就會將其標記爲GC_REACHABLE,表示還正在被引用着,以下圖所示的link1就是這種狀況。
除了將link1標記爲可達的以外,python垃圾回收器,還會從當前可達節點依次遍歷全部可達的節點,好比從link1能夠到達link2和link3,但link3已經被放到Unreachable列表中,所以還須要將link3再移回到Object to Scan列表中,表示對象仍是能夠觸達的。最終的結果以下圖所示,只有link4會被回收掉:
標記-清除法雖然能夠解決循環引用的問題,可是缺點也比較明顯,就是須要python垃圾回收器對python對象執行兩遍掃描,而每次掃描,python解釋器就會暫停處理其餘事情,等到掃描結束後才能恢復正常。這個過程就比如:圖書管理員要對圖書館進行清潔整理,那麼將會關閉圖書館,直到收拾乾淨後才能從新打開圖書館,供同窗們使用。
那既然在python垃圾回收過程當中,會暫停整個應用程序,有沒有更好的優化方案呢?答案是確定的。在python解釋器中,對象的存活時間是不同的:
這樣區分對象後,就能夠節省每次掃描的時間(不須要全部對象都掃描),重而能提高垃圾回收的速度。
python中結合着上面列出的三種類型的對象分了三個對象代(0,1,2),它們其實對應了3個鏈表:每個新生對象在generation zero中,若是它在一輪gc掃描中活了下來,那麼它將被移至generation one,在這一個對象代掃描次數將會減小;若是它又活過了一輪gc,它又將被移至generation two,在這一個對象代對象掃描次數將會更少。
python解釋器只會在觸發某個條件時,纔會去執行垃圾回收。這個條件就是當python分配對象的次數和取消分配對象的次數(引用計數變爲0)作差值高於某個閾值,咱們能夠經過python提供的方法來查看這個閾值。
def threshold_gc(): # 獲取閾值 print(gc.get_threshold()) # 可設置閾值 gc.set_threshold(800, 10, 10) print(gc.get_threshold()) # 運行結果 (700, 10, 10) (800, 10, 10)
上面程序運行結果中值的含義以下:
此外能夠本身根據狀況,調用set_threshold()方法來調整垃圾回收的頻率。好比:set_threshold(700,10,5),至關於增長了對2代對象的掃描頻率。
gc這個庫中還有一些很好玩的函數,你們能夠了解下(更多方法能夠參考官方文檔):
def gc_method(): # 啓動垃圾回收 gc.enable() # 停用垃圾回收 gc.disable() # 手動指定垃圾回收,參數能夠指定垃圾回收的代數,不填寫參數就是徹底的垃圾回收 gc.collect() # 設置垃圾回收的標誌,多用於內存泄漏的檢測 gc.set_debug(gc.DEBUG_LEAK) # 返回一個對象的引用列表 gc.get_referrers()
在python中,內存管理機制被抽象成分層次的結構,從python解釋器Cpython的源碼obmallic.c的註釋中抓取了對內存分層的描述:
/* Object-specific allocators _____ ______ ______ ________ [ int ] [ dict ] [ list ] ... [ string ] Python core | +3 | <----- Object-specific memory -----> | <-- Non-object memory --> | _______________________________ | | [ Python's object allocator ] | | +2 | ####### Object memory ####### | <------ Internal buffers ------> | ______________________________________________________________ | [ Python's raw memory allocator (PyMem_ API) ] | +1 | <----- Python memory (under PyMem manager's control) ------> | | __________________________________________________________________ [ Underlying general-purpose allocator (ex: C library malloc) ] 0 | <------ Virtual memory allocated for the python process -------> | ========================================================================= _______________________________________________________________________ [ OS-specific Virtual Memory Manager (VMM) ] -1 | <--- Kernel dynamic storage allocation & management (page-based) ---> | __________________________________ __________________________________ [ ] [ ] -2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> | */
python的內存這麼分層設計,最根本的目的仍是爲了提升python的執行性能,由於若是不分層,頻繁的調用malloc和free,很是的耗費系統資源,會產生性能問題。而分層以後,第1層和第2層充當了內存池的做用,根據分配的內存大小不一樣,交給不一樣的層去處理,減小了頻繁的調用malloc。
本文介紹了python中垃圾回收的三種方式,以及python內存的分層管理方式,屬於比較深層次的python知識,不過相信也能夠幫助你瞭解python的內存管理方式。若是在以後找工做過程當中再被面試官問道"python垃圾回收機制"這樣的問題,假如你能將文中的內容講出來絕對是加分項。