Python 內存管理機制和垃圾回收機制
內存管理
Python中的內存管理機制的層次結構提供了4層,其中最底層則是C運行的malloc
和free
接口,往上的三層才是由Python實現並且維護的。
第一層則是在第0層的基礎之上對其提供的接口進行了統一的封裝,這是因為雖然不同的操作系統都提供標准定義的內存管理接口,但是對於某些特殊的情況不同的操作系統都不同的行為,比如說調用malloc(0),有的操作系統會返回NULL,表示內存申請失敗;然而有的操作系統會返回一個貌似正常的指針,但是這個指針所指的內存並不是有效的。為了廣泛的移植性,Python必須保證相同的語義一定代表相同的運行行為。
在第二層內存管理機制上,Python構建了更高抽象的內存管理策略,比如說一些常用對象,包括整數對象、字符串對象等等。
第三層主要是對象緩沖池機制,它基於在第二層的內存池。
內存池
Python為了避免頻繁的申請和刪除內存所造成系統切換於用戶態和核心態的開銷,從而引入了內存池機制,專門用來管理小內存的申請和釋放。整個小塊內存的內存池可以視為一個層次結構,其一共分為4層,從下之上分別是block、pool、arena和內存池。需要說明的是:block、pool和area都是代碼中可以找到的實體,而最頂層的內存池只是一個概念上的東西,表示Python對於整個小塊內存分配和釋放行為的內存管理機制。
注意,內存大小以256字節為界限,大於則通過malloc進行分配,小於則通過內存池分配。
如下圖:
block:最小的內存單元,大小為8的整數倍。有很多種類的block,不同種類的block都有不同的內存大小,申請內存的時候只需要找到適合自身大小的block即可,當然申請的內存也是存在一個上限,如果超過這個上限,則退化到使用最底層的malloc進行申請。
pool:一個pool管理着一堆有固定大小的內存塊,其大小通常為一個系統內存頁的大小。
arena:多個pool組合成一個arena。
內存池:一個整體的概念。
參考文章:
垃圾回收
Python的GC模塊主要運用了引用計數來跟蹤和回收垃圾。在引用計數的基礎上,還可以通過“標記-清除”解決容器對象可能產生的循環引用的問題。通過分代回收以空間換取時間進一步提高垃圾回收的效率。
引用計數
原理:當一個對象的引用被創建或者復制時,對象的引用計數加1;當一個對象的引用被銷毀時,對象的引用計數減1,當對象的引用計數減少為0時,就意味着對象已經再沒有被使用了,可以將其內存釋放掉。
優點:引用計數有一個很大的優點,即實時性,任何內存,一旦沒有指向它的引用,就會被立即回收,而其他的垃圾收集技術必須在某種特殊條件下才能進行無效內存的回收。
缺點:但是它也有弱點,引用計數機制所帶來的維護引用計數的額外操作與Python運行中所進行的內存分配和釋放,引用賦值的次數是成正比的,這顯然比其它那些垃圾收集技術所帶來的額外操作只是與待回收的內存數量有關的效率要低。同時,引用技術還存在另外一個很大的問題-循環引用,因為對象之間相互引用,每個對象的引用都不會為0,所以這些對象所占用的內存始終都不會被釋放掉。如下:
a = []
b = []
a.append(b)
b.append(a)
print a
[[[…]]]
print b
[[[…]]]
標記-清除
標記-清除只關注那些可能會產生循環引用的對象,顯然,像是PyIntObject、PyStringObject這些不可變對象是不可能產生循環引用的,因為它們內部不可能持有其它對象的引用。Python中的循環引用總是發生在container對象之間,也就是能夠在內部持有其它對象的對象,比如list、dict、class等等。這也使得該方法帶來的開銷只依賴於container對象的的數量???
原理:1. 尋找跟對象(root object)的集合作為垃圾檢測動作的起點,跟對象也就是一些全局引用和函數棧中的引用,這些引用所指向的對象是不可被刪除的;2. 從root object集合出發,沿着root object集合中的每一個引用,如果能夠到達某個對象,則說明這個對象是可達的,那么就不會被刪除,這個過程就是垃圾檢測階段;3. 當檢測階段結束以后,所有的對象就分成可達和不可達兩部分,所有的可達對象都進行保留,其它的不可達對象所占用的內存將會被回收,這就是垃圾回收階段。(底層采用的是鏈表將這些集合的對象連接在一起)
缺點:標記和清除的過程效率不高。
分代回收
原理:將系統中的所有內存塊根據其存活時間划分為不同的集合,每一個集合就成為一個“代”,Python默認定義了三代對象集合,垃圾收集的頻率隨着“代”的存活時間的增大而減小。也就是說,活得越長的對象,就越不可能是垃圾,就應該減少對它的垃圾收集頻率。那么如何來衡量這個存活時間:通常是利用幾次垃圾收集動作來衡量,如果一個對象經過的垃圾收集次數越多,可以得出:該對象存活時間就越長。
參考文章