在Java中,對象所佔用的內存在對象再也不使用後會自動被回收。這些工做是由一個叫垃圾回收器 (Garbage Collector
)的進程完成的。python
python和其餘不少高級語言同樣,都自帶垃圾回收機制,即GC機制。算法
Python中的垃圾回收是以引用計數爲主,標記-清除和分代收集爲輔。引用計數最大缺陷就是循環引用的問題,因此Python採用了輔助方法。緩存
注意:app
一、垃圾回收時,Python不能進行其它的任務,頻繁的垃圾回收將大大下降Python的工做效率;函數
二、Python只會在特定條件下,自動啓動垃圾回收(垃圾對象少就不必回收)spa
三、當Python運行時,會記錄其中分配對象(object allocation)和取消分配對象(object deallocation)的次數。當二者的差值高於某個閾值時,垃圾回收纔會啓動。操作系統
Python內部使用引用計數,來保持追蹤內存中的對象,全部對象都有引用計數。指針
原理code
引用計數法的原理是每一個對象維護一個ob_refcnt,用來記錄當前對象被引用的次數,也就是來追蹤到底有多少引用指向了這個對象。當一個對象有新的引用時,它的ob_refcnt就會增長,當引用它的對象被刪除,它的ob_refcnt就會減小。當引用計數爲0時,該對象生命就結束了。對象
源碼以下:
// object.h struct _object { Py_ssize_t ob_refcnt; # 引用計數值 struct PyTypeObject *ob_type; } PyObject;
引用計數增長的狀況:
引用計數減小狀況
引用計數法有很明顯的優勢:
原始的引用計數法也有明顯的缺點:
# 循環引用的示例: list1 = [] list2 = [] list1.append(list2) list2.append(list1)
對於現現在強大的硬件來講,缺點維護引用計數消耗資源還尚可接受,可是循環引用致使內存泄漏卻無疑是致命的,所以python還引入了新的回收機制(標記清除和分代收集)
針對循環引用的狀況:咱們有一個「孤島」或是一組未使用的、互相指向的對象,可是誰都沒有外部引用。換句話說,咱們的程序再也不使用這些節點對象了,因此咱們但願Python的垃圾回收機制可以足夠智能去釋放這些對象並回收它們佔用的內存空間。可是這不可能,由於全部的引用計數都是1而不是0。Python的引用計數算法不可以處理互相指向本身的對象。你的代碼也許會在不經意間包含循環引用而且你並未意識到。事實上,當你的Python程序運行的時候它將會創建必定數量的「浮點數垃圾」,Python的GC不可以處理未使用的對象由於應用計數值不會到零。
這就是爲何Python要引入Generational GC算法的緣由!
『標記清除(Mark—Sweep)』算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收算法。它分爲兩個階段:第一階段是標記階段,GC會把全部的『活動對象』打上標記,第二階段是把那些沒有標記的對象『非活動對象』進行回收。那麼GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?
對象之間經過引用(指針)連在一塊兒,構成一個有向圖,對象構成這個有向圖的節點,而引用關係構成這個有向圖的邊。從根對象(root object)出發,沿着有向邊遍歷對象,可達的(reachable)對象標記爲活動對象,不可達的對象就是要被清除的非活動對象。根對象就是全局變量、調用棧、寄存器。
標記清除算法做爲Python的輔助垃圾收集技術主要處理的是一些容器對象,好比list、dict、tuple,instance等,由於對於字符串、數值對象是不可能形成循環引用問題。Python使用一個雙向鏈表將這些容器對象組織起來。不過,這種簡單粗暴的標記清除算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描全部對象。
標記清除算法在申請內存時,全部容器對象的頭部又加上了PyGC_Head來實現「標記-清除」機制。任何一個python對象都分爲兩部分: PyObject_HEAD + 對象自己數據
源碼以下:
// objimpl.h typedef union _gc_head { struct { union _gc_head *gc_next; union _gc_head *gc_prev; Py_ssize_t gc_refs; } gc; long double dummy; /* force worst-case alignment */ } PyGC_Head;
python在建立對象以前,會建立一個鏈表,零代鏈表,只不過這個鏈表是空的。每當你建立一個對象,python便會將其加入到零代鏈表。
python隔代回收的核心:對鏈子上的那些明明沒有被引用但引用計數卻不是零的對象進行引用計數減去一,看看你是否是垃圾。若是被引用屢次減去一以後仍不爲零,那麼會在零代鏈表當中繼續被清理,直至引用計數爲零。由於若是沒有變量指向它,或者做爲函數的參數,列表的元素等等,那麼它就始終是零代鏈表中被清理的對象。當零代鏈表被清理達到必定次數時,會清理一代鏈表。一代鏈表被清理達到必定次數時,會清理二代鏈表。
所以清理的頻率最高的是零代鏈表,其次是一代鏈表,再是二代鏈表。
原理:
內存池(金字塔):