轉自:https://juejin.im/post/5b34b117f265da59a50b2fbe,作者:
Python垃圾回收(GC)三層心法,你了解到第幾層丽柿?
垃圾回收機制應該是面試最常問的問題了恢准,那么Python中的垃圾回收機制(Garbage Collection)是怎么解決的呢魂挂?我記得每一本python入門的書籍都會說python中請不要擔心內存泄漏這個 問題,那么這個背后又是什么原理馁筐,今天就來818涂召。
Python中的GC算法
分為下三點:引用計數/標記-清除/分代回收
·引用計數(主要)
剛開始學習Python的時候總是會有人告訴你,萬物皆對象是一大特色敏沉。在Python中每一個對象的核心就是一個結構體PyObject果正,它的內部有一個引用計數器(ob_refcnt)。
引用計數的意思就是盟迟,一個對象在它剛被New出來呱呱(gugu不是guagua)墜地的時候因為被New方法引用了所以他的引用計數就是1舱卡,如果它被引用(也就是在之前的基礎上 例如:b=a,被丟入函數列表等等被引用就會在引用計數上加1)队萤,如果引用它的對象被刪除的時候(在之前的基礎上DEL b)那么它的引用計數就會減少一一直到當它的引用計數變?yōu)?的時候轮锥,垃圾回收機制就會找上門做掉它(回收),腦補一下 :開門我是查水表的要尔。
優(yōu)點/缺點:
因為引用計數是GC主要方法舍杜,來看一下優(yōu)缺點。
優(yōu):
簡單赵辕,實時性(一旦為零就不跟你多BB既绩,做掉)
缺:
·?維護性高(簡單實時,但是額外占用了一部分資源还惠,雖然邏輯簡單饲握,但是麻煩。好比你吃草莓蚕键,吃一次洗一下手救欧,而不是吃完洗手。)
·?不能解決的情況:--->循環(huán)引用(如下):
說實話感覺還有點像死鎖的問題锣光,這種問題出現在可以循環(huán)的結構中List Dict Object等等笆怠,如上代碼a、b間的引用都為1誊爹,而a蹬刷、b被引用的對象刪除后都各自減去1(所以他們各自的引用計數還是1),這時候就尷尬了啊频丘,都是1就有了免死金牌(一直是1不會變化了)办成。這樣的情況單單靠引用計數就無法解決了。?也為我們引入了下面的主題 標記-清除
·標記-清除:?標記清除就是用來解決循環(huán)引用的問題的只有容器對象才會出現引用循環(huán)搂漠,比如列表迂卢、字典、類、元組冷守。 首先刀崖,為了追蹤容器對象,需要每個容器對象維護兩個額外的指針拍摇, 用來將容器對象組成一個鏈表亮钦,指針分別指向前后兩個容器對象,方便插入和刪除操作充活。試想一下蜂莉,現在有兩種情況:
A:
B:
Okay,現在開始說正題混卵。在標記-清除算法中映穗,有兩個集中營,一個是root鏈表(root object)幕随,另外一個是unreachable鏈表蚁滋。
·?對于情景A,原來再未執(zhí)行DEL語句的時候赘淮,a,b的引用計數都為2(init+append=2)辕录,但是在DEL執(zhí)行完以后,a,b引用次數互相減1梢卸。a,b陷入循環(huán)引用的圈子中走诞,然后標記-清除算法開始出來做事,找到其中一端a,開始拆這個a,b的引用環(huán)(我們從A出發(fā)蛤高,因為它有一個對B的引用蚣旱,則將B的引用計數減1;然后順著引用達到B戴陡,因為B有一個對A的引用塞绿,同樣將A的引用減1,這樣猜欺,就完成了循環(huán)引用對象間環(huán)摘除位隶。),去掉以后發(fā)現开皿,a,b循環(huán)引用變?yōu)榱?,所以a,b就被處理到unreachable鏈表中直接被做掉篮昧。
·?對于情景B,簡單一看那b取環(huán)后引用計數還為1赋荆,但是a取環(huán),就為0了懊昨。這個時候a已經進入unreachable鏈表中窄潭,已經被判為死刑了,但是這個時候酵颁,root鏈表中有b嫉你。如果a被做掉月帝,那世界上還有什么正義...?,在root鏈表中的b會被進行引用檢測引用了a幽污,如果a被做掉了嚷辅,那么b就...涼涼,一審完事距误,二審a無罪簸搞,所以被拉到了root鏈表中。
QA:?為什么要搞這兩個鏈表
之所以要剖成兩個鏈表准潭,是基于這樣的一種考慮:現在的unreachable可能存在被root鏈表中的對象趁俊,直接或間接引用的對象,這些對象是不能被回收的刑然,一旦在標記的過程中寺擂,發(fā)現這樣的對象,就將其從unreachable鏈表中移到root鏈表中泼掠;當完成標記后怔软,unreachable鏈表中剩下的所有對象就是名副其實的垃圾對象了,接下來的垃圾回收只需限制在unreachable鏈表中即可武鲁。
分代回收:
了解分類回收爽雄,首先要了解一下,GC的閾值沐鼠,所謂閾值就是一個臨界點的值挚瘟。隨著你的程序運行,Python解釋器保持對新創(chuàng)建的對象饲梭,以及因為引用計數為零而被釋放掉的對象的追蹤乘盖。從理論上說,創(chuàng)建==釋放數量應該是這樣子憔涉。但是如果存在循環(huán)引用的話订框,肯定是創(chuàng)建>釋放數量,當創(chuàng)建數與釋放數量的差值達到規(guī)定的閾值的時候兜叨,當當當當~分代回收機制就登場啦穿扳。
垃圾回收=垃圾檢測+釋放。
分代回收思想將對象分為三代(generation 0,1,2)国旷,0代表幼年對象矛物,1代表青年對象,2代表老年對象跪但。根據弱代假說(越年輕的對象越容易死掉履羞,老的對象通常會存活更久。)?新生的對象被放入0代,如果該對象在第0代的一次gc垃圾回收中活了下來忆首,那么它就被放到第1代里面(它就升級了)爱榔。如果第1代里面的對象在第1代的一次gc垃圾回收中活了下來,它就被放到第2代里面糙及。gc.set_threshold(threshold0[,threshold1[,threshold2]])設置gc每一代垃圾回收所觸發(fā)的閾值详幽。從上一次第0代gc后,如果分配對象的個數減去釋放對象的個數大于threshold0丁鹉,那么就會對第0代中的對象進行gc垃圾回收檢查妒潭。?從上一次第1代gc后,如過第0代被gc垃圾回收的次數大于threshold1揣钦,那么就會對第1代中的對象進行gc垃圾回收檢查雳灾。同樣,從上一次第2代gc后冯凹,如過第1代被gc垃圾回收的次數大于threshold2谎亩,那么就會對第2代中的對象進行gc垃圾回收檢查。
這算是簡單的講完了Python的GC,打完收工。