點(diǎn)評:當(dāng)面試官問到這個問題的時候胜卤,一個展示自己的機(jī)會就擺在面前了疆导。你要先反問面試官:“你說的是官方的CPython解釋器嗎?”葛躏。這個反問可以展示出你了解過Python解釋器的不同的實(shí)現(xiàn)版本澈段,而且你也知道面試官想問的是CPython。當(dāng)然舰攒,很多面試官對不同的Python解釋器底層實(shí)現(xiàn)到底有什么差別也沒有概念败富。所以,千萬不要覺得面試官一定比你強(qiáng)摩窃,懷揣著這份自信可以讓你更好的完成面試囤耳。
Python提供了自動化的內(nèi)存管理,也就是說內(nèi)存空間的分配與釋放都是由Python解釋器在運(yùn)行時自動進(jìn)行的偶芍,自動管理內(nèi)存功能極大的減輕程序員的工作負(fù)擔(dān)充择,也能夠幫助程序員在一定程度上解決內(nèi)存泄露的問題。以CPython解釋器為例匪蟀,它的內(nèi)存管理有三個關(guān)鍵點(diǎn):引用計(jì)數(shù)椎麦、標(biāo)記清理、分代收集材彪。
引用計(jì)數(shù):對于CPython解釋器來說观挎,Python中的每一個對象其實(shí)就是PyObject結(jié)構(gòu)體,它的內(nèi)部有一個名為ob_refcnt 的引用計(jì)數(shù)器成員變量段化。程序在運(yùn)行的過程中ob_refcnt的值會被更新并藉此來反映引用有多少個變量引用到該對象嘁捷。當(dāng)對象的引用計(jì)數(shù)值為0時,它的內(nèi)存就會被釋放掉显熏。
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
以下情況會導(dǎo)致引用計(jì)數(shù)加1:
對象被創(chuàng)建
對象被引用
對象作為參數(shù)傳入到一個函數(shù)中
對象作為元素存儲到一個容器中
以下情況會導(dǎo)致引用計(jì)數(shù)減1:
用del語句顯示刪除對象引用
對象引用被重新賦值其他對象
一個對象離開它所在的作用域
持有該對象的容器自身被銷毀
持有該對象的容器刪除該對象
可以通過sys模塊的getrefcount函數(shù)來獲得對象的引用計(jì)數(shù)雄嚣。引用計(jì)數(shù)的內(nèi)存管理方式在遇到循環(huán)引用的時候就會出現(xiàn)致命傷,因此需要其他的垃圾回收算法對其進(jìn)行補(bǔ)充。
標(biāo)記清理:CPython使用了“標(biāo)記-清理”(Mark and Sweep)算法解決容器類型可能產(chǎn)生的循環(huán)引用問題缓升。
該算法在垃圾回收時分為兩個階段:標(biāo)記階段鼓鲁,遍歷所有的對象,如果對象是可達(dá)的(被其他對象引用)港谊,那么就標(biāo)記該對象為可達(dá)骇吭;清除階段,再次遍歷對象歧寺,如果發(fā)現(xiàn)某個對象沒有標(biāo)記為可達(dá)燥狰,則就將其回收。
CPython底層維護(hù)了兩個雙端鏈表斜筐,一個鏈表存放著需要被掃描的容器對象(姑且稱之為鏈表A)龙致,另一個鏈表存放著臨時不可達(dá)對象(姑且稱之為鏈表B)。為了實(shí)現(xiàn)“標(biāo)記-清理”算法奴艾,鏈表中的每個節(jié)點(diǎn)除了有記錄當(dāng)前引用計(jì)數(shù)的ref_count變量外净当,還有一個gc_ref變量,這個gc_ref是ref_count的一個副本蕴潦,所以初始值為ref_count的大小像啼。執(zhí)行垃圾回收時,首先遍歷鏈表A中的節(jié)點(diǎn)潭苞,并且將當(dāng)前對象所引用的所有對象的gc_ref減1忽冻,這一步主要作用是解除循環(huán)引用對引用計(jì)數(shù)的影響。再次遍歷鏈表A中的節(jié)點(diǎn)此疹,如果節(jié)點(diǎn)的gc_ref值為0僧诚,那么這個對象就被標(biāo)記為“暫時不可達(dá)”(GC_TENTATIVELY_UNREACHABLE)并被移動到鏈表B中;如果節(jié)點(diǎn)的gc_ref不為0蝗碎,那么這個對象就會被標(biāo)記為“可達(dá)“(GC_REACHABLE)湖笨,對于”可達(dá)“對象,還要遞歸的將該節(jié)點(diǎn)可以到達(dá)的節(jié)點(diǎn)標(biāo)記為”可達(dá)“蹦骑;鏈表B中被標(biāo)記為”可達(dá)“的節(jié)點(diǎn)要重新放回到鏈表A中慈省。在兩次遍歷之后,鏈表B中的節(jié)點(diǎn)就是需要釋放內(nèi)存的節(jié)點(diǎn)眠菇。
分代回收:在循環(huán)引用對象的回收中边败,整個應(yīng)用程序會被暫停,為了減少應(yīng)用程序暫停的時間捎废,Python 通過分代回收(空間換時間)的方法提高垃圾回收效率笑窜。分代回收的基本思想是:對象存在的時間越長,是垃圾的可能性就越小登疗,應(yīng)該盡量不對這樣的對象進(jìn)行垃圾回收排截。CPython將對象分為三種世代分別記為0、1、2匾寝,每一個新生對象都在第0代中搬葬,如果該對象在一輪垃圾回收掃描中存活下來荷腊,那么它將被移到第1代中艳悔,存在于第1代的對象將較少的被垃圾回收掃描到;如果在對第1代進(jìn)行垃圾回收掃描時女仰,這個對象又存活下來猜年,那么它將被移至第2代中,在那里它被垃圾回收掃描的次數(shù)將會更少疾忍。分代回收掃描的門限值可以通過gc模塊的get_threshold函數(shù)來獲得乔外,該函數(shù)返回一個三元組,分別表示多少次內(nèi)存分配操作后會執(zhí)行0代垃圾回收一罩,多少次0代垃圾回收后會執(zhí)行1代垃圾回收杨幼,多少次1代垃圾回收后會執(zhí)行2代垃圾回收。需要說明的是聂渊,如果執(zhí)行一次2代垃圾回收差购,那么比它年輕的代都要執(zhí)行垃圾回收。如果想修改這幾個門限值汉嗽,可以通過gc模塊的set_threshold函數(shù)來做到欲逃。