黄舟2017-04-18 09:45:39
引用計數
為每個記憶體物件維護一個引用計數。
當有新的引用指向某物件時就將該物件的參考計數加一,當指向該物件的引用被銷毀時將該計數減一,當計數歸零時,就回收該物件所佔用的記憶體資源。
標記-清除
分兩個步驟,一是標記,即從眾多的記憶體物件中區分出不會再被使用的垃圾物件;
二是清除,即把標記的垃圾物件清除掉。標記的時候需要確定記憶體物件的集合Root set,集合裡的物件都是可以存取的。如果Root set中的物件引用了其他的對象,那麼被引用的物件就不能被標記為垃圾對象。然後從Root set出發,遞歸遍歷Root set能存取到的所有對象,進行標記為不是垃圾對象。遍歷結束後,沒有被標記的就是垃圾物件。
分代採集
根據一個統計上的結論,如果一個記憶體物件在某次Mark過程中發現不是垃圾,那麼它短期內成為垃圾的可能性就很小。分代收集將那些在多次垃圾收集過程中都沒有被標記為垃圾物件的記憶體物件集中到另外一個區域——年老的區域,即這個區域中的記憶體物件年齡比較大。因為老年區域內記憶體物件短期內變成垃圾的機率很低,所以這些區域的垃圾收集頻率可以降低,相對的,對年輕區域內的物件進行高頻率的垃圾收集。這樣可以提高垃圾收集的整體效能。
在CPython中,大多數物件的生命週期都是透過物件的引用計數來管理的。引用計數是一種最直觀、最簡單的垃圾收集計數,與其他主流GC演算法比較,它的最大優點是實時性,即任何內存,一旦沒有指向它的引用,就會立即被回收。
然而引用計數存在兩個比較麻煩的缺陷:
當程式中出現循環引用時,引用計數無法偵測出來,被循環引用的記憶體物件就成了無法回收的內存,引起記憶體外洩。如:
list1 = []
list1.append(list1)
del list1
list1
循环引用了自身,第二行执行完后,list1
的GC变成了2,执行完del
操作后,list1
的引用計數變成1,並沒有歸零,list1的記憶體空間並沒有被釋放,造成記憶體外洩。
維護參考計數需要額外的操作。
在每次內存物件唄引用或引用被銷毀時都需要修改引用計數,這類操作稱為footprint
。引用计数的footprint
是很高的,使得程式的整體效能受到很大的影響。
為了解決循環引用的問題,CPython特別設計了一個模組-GC module
,其主要作用就是檢查出循環引用的垃圾對象,並清除他們。這個模組的實現,實際上也是引入了前面提到的兩種主流的垃圾收集技術——標記清除和分代收集。
為了解決引用計數的效能問題,盡量再記憶體的分配和釋放上獲得最高的效率,Python因此也設計了大量的記憶體池機制。
PHP中文网2017-04-18 09:45:39
Python GC主要使用引用計數(reference counting)來追蹤和回收垃圾。在引用計數的基礎上,透過「標記-清除」(mark and sweep)解決容器物件可能產生的循環引用問題,透過「分代回收」(generation collection)以空間換時間的方法提高垃圾回收效率。
PyObject是每個物件必有的內容,其中ob_refcnt
就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt
就会增加,当引用它的对象被删除,它的ob_refcnt
就會減少.引用計數為0時,該物件生命就結束了。
優點:
簡單
實時性
缺點:
維護引用計數消耗資源
循環引用
基本想法是先按需分配,等到沒有空閒記憶體的時候從暫存器和程式棧上的引用出發,遍歷以物件為節點、以引用為邊構成的圖,把所有可以存取到的物件打上標記,然後清掃一遍記憶體空間,把所有沒標記的物件釋放。
分代回收的整體思想是:將系統中的所有內存塊根據其存活時間劃分為不同的集合,每個集合就成為一個“代”,垃圾收集頻率隨著“代”的存活時間的增大而減小,存活時間通常利用經過幾次垃圾回收來度量。
Python預設定義了三代物件集合,索引數越大,物件存活時間越長。