目錄鏈接:http://www.itdecent.cn/p/e1e201bea601
計數(shù)引用
Python 中一切皆對象,因此你所看到的一切變量, 本質上都是對象的一個指針。
當一個對象的引用計數(shù)(指針數(shù)) 為 0 的時候, 說明這個對象永不可達, 它也就成為了垃圾需要被回收。
例子:
import sys
a = []
# 兩次引用,一次來自 a,一次來自 getrefcount
print(sys.getrefcount(a))
def func(intmp):
# 四次引用,a,python 的函數(shù)調(diào)用棧,函數(shù)參數(shù),和 getrefcount
print(sys.getrefcount(intmp))
func(a)
# 兩次引用,一次來自 a,一次來自 getrefcount,函數(shù) func 調(diào)用已經(jīng)不存在
print(sys.getrefcount(a))

sys.getrefcount() 這個函數(shù), 可以查看一個變量的引用次數(shù),getrefcount 本身也會引用一次計數(shù)。
在函數(shù)調(diào)用發(fā)生的時候, 會產(chǎn)生額外的兩次引用,一次來自函數(shù)棧, 另一個是函數(shù)參數(shù)。
例子:
import sys
a = []
print(sys.getrefcount(a)) # 兩次
b = a
print(sys.getrefcount(a)) # 三次
c = b
d = b
e = c
f = e
g = d
print(sys.getrefcount(a)) # 八次

a、 b、 c、 d、 e、 f、 g 這些變量全部指代的是同一個對象,最后一共會有8次引用。
python里每一個東西都是對象,它們的核心就是一個結構體:PyObject
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每個對象必有的內(nèi)容,其中ob_refcnt就是做為引用計數(shù)。當一個對象有新的引用時,它的ob_refcnt就會增加,當引用它的對象被刪除,它的ob_refcnt就會減少
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加計數(shù)
#define Py_DECREF(op) \ //減少計數(shù)
if (--(op)->ob_refcnt != 0) \
; \
else \
__Py_Dealloc((PyObject *)(op))
當引用計數(shù)為0時,該對象生命就結束了。
引用計數(shù)機制的優(yōu)點:
1、簡單
2、實時性:一旦沒有引用,內(nèi)存就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收內(nèi)存的時間分攤到了平時。
引用計數(shù)機制的缺點:
1、維護引用計數(shù)消耗資源
2、循環(huán)引用
引用計數(shù)機制無法處理循環(huán)引用的情況,如下代碼
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1與list2相互引用,如果不存在其他對象對它們的引用,list1與list2的引用計數(shù)也仍然為1,所占用的內(nèi)存永遠無法被回收,這將是致命的。注定python還將引入新的回收機制。(標記清除(mark-sweep) 和分代收集(generational) )

如果想手動釋放內(nèi)存,可以先調(diào)用del a 來刪除一個對象;然后強制調(diào)用 gc.collect(), 即可手動啟動垃圾回收。
標記-清除算法
對于一個有向圖, 如果從一個節(jié)點出發(fā)進行遍歷, 并標記其經(jīng)過的所有節(jié)點;那么, 在遍歷結束后, 所有沒有被標記的節(jié)點, 我們就稱之為不可達節(jié)點。 顯而易見, 這些節(jié)點的存在是沒有任何意義的,我們就需要對它們進行垃圾回收。當然, 每次都遍歷全圖, 對于 Python 而言是一種巨大的性能浪費。 所以, 在 Python 的垃圾回收實現(xiàn)中, mark-sweep 使用雙向鏈表維護了一個數(shù)據(jù)結構, 并且只考慮容器類的對象(只有容器類對象才有可能產(chǎn)生循環(huán)引用) 。
現(xiàn)在有兩種情況:
A:
a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a
del b
B:
a=[1,3]
b=[2,4]
a.append(b)
b.append(a)
del a
在標記-清除算法中,有兩個集中營,一個是root鏈表(root object),另外一個是unreachable鏈表。
對于情景A,原來再未執(zhí)行DEL語句的時候,a,b的引用計數(shù)都為2(init+append=2),但是在DEL執(zhí)行完以后,a,b引用次數(shù)互相減1。a,b陷入循環(huán)引用的圈子中,然后標記-清除算法開始出來做事,找到其中一端a,開始拆這個a,b的引用環(huán)(我們從A出發(fā),因為它有一個對B的引用,則將B的引用計數(shù)減1;然后順著引用達到B,因為B有一個對A的引用,同樣將A的引用減1,這樣,就完成了循環(huán)引用對象間環(huán)摘除。),去掉以后發(fā)現(xiàn),a,b循環(huán)引用變?yōu)榱?,所以a,b就被處理到unreachable鏈表中直接被做掉。
對于情景B,簡單一看那b取環(huán)后引用計數(shù)還為1,但是a取環(huán)就為0了(其實不是的)。這個時候a已經(jīng)進入unreachable鏈表中,但是這個時候,root鏈表中有b。如果a被做掉,在root鏈表中的b會被進行引用檢測引用了a,如果a被做掉了,那么b就也就要被做掉這時不對,一審完事,二審a無罪,所以a又被拉回到了root鏈表中。
QA: 為什么要搞這兩個鏈表
之所以要剖成兩個鏈表,是基于這樣的一種考慮:現(xiàn)在的unreachable可能存在被root鏈表中的對象直接或間接引用的對象,這些對象是不能被回收的,一旦在標記的過程中,發(fā)現(xiàn)這樣的對象,就將其從unreachable鏈表中移到root鏈表中;當完成標記后,unreachable鏈表中剩下的所有對象就是名副其實的垃圾對象了,接下來的垃圾回收只需限制在unreachable鏈表中即可。
分代收集算法
Python 將所有對象分為三代。 剛剛創(chuàng)立的對象是第 0 代;經(jīng)過一次垃圾回收后, 依然存在的對象, 便會依次從上一代挪到下一代。 而每一代啟動自動垃圾回收的閾值, 則是可以單獨指定的。當垃圾回收器中新增對象減去刪除對象達到相應的閾值時, 就會對這一代對象啟動垃圾回收。分代收集基于的思想是, 新?的對象更有可能被垃圾回收, 而存活更久的對象也有更高的概率繼續(xù)存活。
調(diào)試內(nèi)存泄漏
雖然有了自動回收機制, 但這也不是萬能的, 難免還是會有漏網(wǎng)之魚。 內(nèi)存泄漏是我們不想見到的, ?且還會嚴重影響性能。 objgraph, 一個非常好用的可視化引用關系的包。
第一個有用的函數(shù)是 show_refs(), 它可以生成清晰的引用關系圖。
通過下面這段代碼和生成的引用調(diào)用圖, 能非常直觀地發(fā)現(xiàn)有兩個 list 互相引用, 說明這里極有可能引起內(nèi)存泄露。 這樣, 再去代碼層排查就容易多了。
import objgraph
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
objgraph.show_refs([a])

需要安裝第三方庫:pip install objgraph
并使用https://graphviz.gitlab.io/_pages/Download/Download_windows.html連接中工具graphviz,并用gvedit.exe查看.dot文件

第二個非常有用的函數(shù)是 show_backrefs()
import objgraph
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
objgraph.show_backrefs([a])


這個 API有很多有用的參數(shù), 比如層數(shù)限制(max_depth) 、 寬度限制(too_many) 、 輸出格式控制
(filename output) 、 節(jié)點過濾(filter, extra_ignore) 等。 可參看:https://mg.pov.lt/objgraph/
參考資料:
極客時間 Python核心技術與實戰(zhàn)學習
Python核心技術與實戰(zhàn)(極客時間)鏈接:
http://gk.link/a/103Sv
Python垃圾回收(GC)三層心法,你了解到第幾層?:
https://juejin.im/post/5b34b117f265da59a50b2fbe
GitHub鏈接:
https://github.com/lichangke/LeetCode
個人Blog:
https://lichangke.github.io/
歡迎大家來一起交流學習