資訊內(nèi)容
剖析Python垃圾回收機(jī)制
PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
引用計(jì)數(shù)器為主、分代碼回收和標(biāo)記清除為輔PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
1.1 大管家refchain在Python的C源碼中有一個(gè)名為refchain的環(huán)狀雙向鏈表,這個(gè)鏈表比較牛逼了,因?yàn)镻ython程序中一旦創(chuàng)建對(duì)象都會(huì)把這個(gè)對(duì)象添加到refchain這個(gè)鏈表中。也就是說(shuō)他保存著所有的對(duì)象。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
1.2 引用計(jì)數(shù)器在refchain中的所有對(duì)象內(nèi)部都有一個(gè)ob_refcnt用來(lái)保存當(dāng)前對(duì)象的引用計(jì)數(shù)器,顧名思義就是自己被引用的次數(shù)。當(dāng)值被多次引用時(shí)候,不會(huì)在內(nèi)存中重復(fù)創(chuàng)建數(shù)據(jù),而是引用計(jì)數(shù)器+1 。 當(dāng)對(duì)象被銷(xiāo)毀時(shí)候同時(shí)會(huì)讓引用計(jì)數(shù)器-1,如果引用計(jì)數(shù)器為0,則將對(duì)象從refchain鏈表中摘除,同時(shí)在內(nèi)存中進(jìn)行銷(xiāo)毀(暫不考慮緩存等特殊情況)。age = 18number = age # 對(duì)象18的引用計(jì)數(shù)器 + 1del age # 對(duì)象18的引用計(jì)數(shù)器 - 1def run(arg): print(arg) run(number) # 剛開(kāi)始執(zhí)行函數(shù)時(shí),對(duì)象18引用計(jì)數(shù)器 + 1,當(dāng)函數(shù)執(zhí)行完畢之后,對(duì)象18引用計(jì)數(shù)器 - 1 。num_list = [11,22,number] # 對(duì)象18的引用計(jì)數(shù)器 + 1復(fù)制代碼1.3 標(biāo)記清除&分代回收基于引用計(jì)數(shù)器進(jìn)行垃圾回收非常方便和簡(jiǎn)單,但他還是存在循環(huán)引用的問(wèn)題,導(dǎo)致無(wú)法正常的回收一些數(shù)據(jù),例如:PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
v1 = [11,22,33] # refchain中創(chuàng)建一個(gè)列表對(duì)象,由于v1=對(duì)象,所以列表引對(duì)象用計(jì)數(shù)器為1.v2 = [44,55,66] # refchain中再創(chuàng)建一個(gè)列表對(duì)象,因v2=對(duì)象,所以列表對(duì)象引用計(jì)數(shù)器為1.v1.append(v2) # 把v2追加到v1中,則v2對(duì)應(yīng)的[44,55,66]對(duì)象的引用計(jì)數(shù)器加1,**終為2.v2.append(v1) # 把v1追加到v1中,則v1對(duì)應(yīng)的[11,22,33]對(duì)象的引用計(jì)數(shù)器加1,**終為2.del v1 # 引用計(jì)數(shù)器-1del v2 # 引用計(jì)數(shù)器-1復(fù)制代碼對(duì)于上述代碼會(huì)發(fā)現(xiàn),執(zhí)行del操作之后,沒(méi)有變量再會(huì)去使用那兩個(gè)列表對(duì)象,但由于循環(huán)引用的問(wèn)題,他們的引用計(jì)數(shù)器不為0,所以他們的狀態(tài):永遠(yuǎn)不會(huì)被使用、也不會(huì)被銷(xiāo)毀。項(xiàng)目中如果這種代碼太多,就會(huì)導(dǎo)致內(nèi)存一直被消耗,直到內(nèi)存被耗盡,程序崩潰。為了解決循環(huán)引用的問(wèn)題,引入了標(biāo)記清除技術(shù),專(zhuān)門(mén)針對(duì)那些可能存在循環(huán)引用的對(duì)象進(jìn)行特殊處理,可能存在循環(huán)應(yīng)用的類(lèi)型有:列表、元組、字典、集合、自定義類(lèi)等那些能進(jìn)行數(shù)據(jù)嵌套的類(lèi)型。標(biāo)記清除:創(chuàng)建特殊鏈表專(zhuān)門(mén)用于保存 列表、元組、字典、集合、自定義類(lèi)等對(duì)象,之后再去檢查這個(gè)鏈表中的對(duì)象是否存在循環(huán)引用,如果存在則讓雙方的引用計(jì)數(shù)器均 - 1 。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
分代回收:對(duì)標(biāo)記清除中的鏈表進(jìn)行優(yōu)化,將那些可能存在循引用的對(duì)象拆分到3個(gè)鏈表,鏈表稱(chēng)為:0/1/2三代,每代都可以存儲(chǔ)對(duì)象和閾值,當(dāng)達(dá)到閾值時(shí),就會(huì)對(duì)相應(yīng)的鏈表中的每個(gè)對(duì)象做一次掃描,除循環(huán)引用各自減1并且銷(xiāo)毀引用計(jì)數(shù)器為0的對(duì)象。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
// 分代的C源碼#define NUM_GENERATIONS 3struct gc_generation generations[NUM_GENERATIONS] = { /* PyGC_Head, threshold, count */ {{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0}, // 0代 {{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0}, // 1代 {{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0}, // 2代};復(fù)制代碼特別注意:0代和1、2代的threshold和count表示的意義不同。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
0代,count表示0代鏈表中對(duì)象的數(shù)量,threshold表示0代鏈表對(duì)象個(gè)數(shù)閾值,超過(guò)則執(zhí)行一次0代掃描檢查。 1代,count表示0代鏈表掃描的次數(shù),threshold表示0代鏈表掃描的次數(shù)閾值,超過(guò)則執(zhí)行一次1代掃描檢查。 2代,count表示1代鏈表掃描的次數(shù),threshold表示1代鏈表掃描的次數(shù)閾值,超過(guò)則執(zhí)行一2代掃描檢查。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
1.4 情景模擬根據(jù)C語(yǔ)言底層并結(jié)合圖來(lái)講解內(nèi)存管理和垃圾回收的詳細(xì)過(guò)程。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
第一步:當(dāng)創(chuàng)建對(duì)象age=19時(shí),會(huì)將對(duì)象添加到refchain鏈表中。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
第二步:當(dāng)創(chuàng)建對(duì)象num_list = [11,22]時(shí),會(huì)將列表對(duì)象添加到 refchain 和 generations 0代中。 PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
第三步:新創(chuàng)建對(duì)象使generations的0代鏈表上的對(duì)象數(shù)量大于閾值700時(shí),要對(duì)鏈表上的對(duì)象進(jìn)行掃描檢查。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
當(dāng)0代大于閾值后,底層不是直接掃描0代,而是先判斷2、1是否也超過(guò)了閾值。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
如果2、1代未達(dá)到閾值,則掃描0代,并讓1代的 count + 1 。如果2代已達(dá)到閾值,則將2、1、0三個(gè)鏈表拼接起來(lái)進(jìn)行全掃描,并將2、1、0代的count重置為0.如果1代已達(dá)到閾值,則講1、0兩個(gè)鏈表拼接起來(lái)進(jìn)行掃描,并將所有1、0代的count重置為0.對(duì)拼接起來(lái)的鏈表在進(jìn)行掃描時(shí),主要就是剔除循環(huán)引用和銷(xiāo)毀垃圾,詳細(xì)過(guò)程為:PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
掃描鏈表,把每個(gè)對(duì)象的引用計(jì)數(shù)器拷貝一份并保存到 gc_refs中,保護(hù)原引用計(jì)數(shù)器。再次掃描鏈表中的每個(gè)對(duì)象,并檢查是否存在循環(huán)引用,如果存在則讓各自的gc_refs減 1 。再次掃描鏈表,將 gc_refs 為 0 的對(duì)象移動(dòng)到unreachable鏈表中;不為0的對(duì)象直接升級(jí)到下一代鏈表中。處理unreachable鏈表中的對(duì)象的 析構(gòu)函數(shù) 和 弱引用,不能被銷(xiāo)毀的對(duì)象升級(jí)到下一代鏈表,能銷(xiāo)毀的保留在此鏈表。析構(gòu)函數(shù),指的就是那些定義了__del__方法的對(duì)象,需要執(zhí)行之后再進(jìn)行銷(xiāo)毀處理。**后將 unreachable 中的每個(gè)對(duì)象銷(xiāo)毀并在refchain鏈表中移除(不考慮緩存機(jī)制)。至此,垃圾回收的過(guò)程結(jié)束。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
1.5 緩存機(jī)制從上文大家可以了解到當(dāng)對(duì)象的引用計(jì)數(shù)器為0時(shí),就會(huì)被銷(xiāo)毀并釋放內(nèi)存。而實(shí)際上他不是這么的簡(jiǎn)單粗暴,因?yàn)榉磸?fù)的創(chuàng)建和銷(xiāo)毀會(huì)使程序的執(zhí)行效率變低。Python中引入了“緩存機(jī)制”機(jī)制。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
例如:引用計(jì)數(shù)器為0時(shí),不會(huì)真正銷(xiāo)毀對(duì)象,而是將他放到一個(gè)名為 free_list 的鏈表中,之后會(huì)再創(chuàng)建對(duì)象時(shí)不會(huì)在重新開(kāi)辟內(nèi)存,而是在free_list中將之前的對(duì)象來(lái)并重置內(nèi)部的值來(lái)使用。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
float類(lèi)型,維護(hù)的free_list鏈表**多可緩存100個(gè)float對(duì)象。 v1 = 3.14 # 開(kāi)辟內(nèi)存來(lái)存儲(chǔ)float對(duì)象,并將對(duì)象添加到refchain鏈表。 print( id(v1) ) # 內(nèi)存地址:4436033488 del v1 # 引用計(jì)數(shù)器-1,如果為0則在rechain鏈表中移除,不銷(xiāo)毀對(duì)象,而是將對(duì)象添加到float的free_list. v2 = 9.999 # 優(yōu)先去free_list中獲取對(duì)象,并重置為9.999,如果free_list為空才重新開(kāi)辟內(nèi)存。 print( id(v2) ) # 內(nèi)存地址:4436033488 # 注意:引用計(jì)數(shù)器為0時(shí),會(huì)先判斷free_list中緩存?zhèn)€數(shù)是否滿了,未滿則將對(duì)象緩存,已滿則直接將對(duì)象銷(xiāo)毀。復(fù)制代碼int類(lèi)型,不是基于free_list,而是維護(hù)一個(gè)small_ints鏈表保存常見(jiàn)數(shù)據(jù)(小數(shù)據(jù)池),小數(shù)據(jù)池范圍:-5 <= value < 257。即:重復(fù)使用這個(gè)范圍的整數(shù)時(shí),不會(huì)重新開(kāi)辟內(nèi)存。 v1 = 38 # 去小數(shù)據(jù)池small_ints中獲取38整數(shù)對(duì)象,將對(duì)象添加到refchain并讓引用計(jì)數(shù)器+1。 print( id(v1)) #內(nèi)存地址:4514343712 v2 = 38 # 去小數(shù)據(jù)池small_ints中獲取38整數(shù)對(duì)象,將refchain中的對(duì)象的引用計(jì)數(shù)器+1。 print( id(v2) ) #內(nèi)存地址:4514343712 # 注意:在解釋器啟動(dòng)時(shí)候-5~256就已經(jīng)被加入到small_ints鏈表中且引用計(jì)數(shù)器初始化為1, # 代碼中使用的值時(shí)直接去small_ints中拿來(lái)用并將引用計(jì)數(shù)器+1即可。另外,small_ints中的數(shù)據(jù)引用計(jì)數(shù)器永遠(yuǎn)不會(huì)為0 # (初始化時(shí)就設(shè)置為1了),所以也不會(huì)被銷(xiāo)毀。復(fù)制代碼str類(lèi)型,維護(hù)unicode_latin1[256]鏈表,內(nèi)部將所有的ascii字符緩存起來(lái),以后使用時(shí)就不再反復(fù)創(chuàng)建。 v1 = "A" print( id(v1) ) # 輸出:4517720496 del v1 v2 = "A" print( id(v1) ) # 輸出:4517720496 # 除此之外,Python內(nèi)部還對(duì)字符串做了駐留機(jī)制,針對(duì)只含有字母、數(shù)字、下劃線的字符串(見(jiàn)源碼Objects/codeobject.c),如果 # 內(nèi)存中已存在則不會(huì)重新在創(chuàng)建而是使用原來(lái)的地址里(不會(huì)像free_list那樣一直在內(nèi)存存活,只有內(nèi)存中有才能被重復(fù)利用)。 v1 = "asdfg" v2 = "asdfg" print(id(v1) == id(v2)) # 輸出:True復(fù)制代碼list類(lèi)型,維護(hù)的free_list數(shù)組**多可緩存80個(gè)list對(duì)象。PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
v1 = [11,22,33] print( id(v1) ) # 輸出:4517628816del v1 v2 = ["你","好"] print( id(v2) ) # 輸出:4517628816復(fù)制代碼 tuple類(lèi)型,維護(hù)一個(gè)free_list數(shù)組且數(shù)組容量20,數(shù)組中元素可以是鏈表且每個(gè)鏈表**多可以容納2000個(gè)元組對(duì)象。元組的free_list數(shù)組在存儲(chǔ)數(shù)據(jù)時(shí),是按照元組可以容納的個(gè)數(shù)為索引找到free_list數(shù)組中對(duì)應(yīng)的鏈表,并添加到鏈表中。v1 = (1,2) print( id(v1) )del v1 # 因元組的數(shù)量為2,所以會(huì)把這個(gè)對(duì)象緩存到free_list[2]的鏈表中。v2 = ("哈哈哈","Alex") # 不會(huì)重新開(kāi)辟內(nèi)存,而是去free_list[2]對(duì)應(yīng)的鏈表中拿到一個(gè)對(duì)象來(lái)使用。print( id(v2) )復(fù)制代碼dict類(lèi)型,維護(hù)的free_list數(shù)組**多可緩存80個(gè)dict對(duì)象 v1 = {"k1":123} print( id(v1) ) # 輸出:4515998128 del v1 v2 = {"name":"哈哈哈","age":18,"gender":"男"} print( id(v1) ) # 輸出:4515998128復(fù)制代碼C語(yǔ)言源碼底層分析PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
相關(guān)免費(fèi)學(xué)習(xí)推薦:python教程(視頻)PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)
以上就是剖析Python垃圾回收機(jī)制的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注少兒編程網(wǎng)其它相關(guān)文章!PFa少兒編程網(wǎng)-Scratch_Python_教程_免費(fèi)兒童編程學(xué)習(xí)平臺(tái)

- 上一篇
python爬蟲(chóng)要用到哪些庫(kù)?
簡(jiǎn)介python爬蟲(chóng)要用到的庫(kù):請(qǐng)求庫(kù):實(shí)現(xiàn)HTTP請(qǐng)求操作urllib:一系列用于操作URL的功能。requests:基于urllib編寫(xiě)的,阻塞式HTTP請(qǐng)求庫(kù),發(fā)出一個(gè)請(qǐng)求,一直等待服務(wù)器響應(yīng)后,程序才能進(jìn)行下一步處理。selenium:自動(dòng)化測(cè)試工具。一個(gè)調(diào)用瀏覽器的driver,通過(guò)這個(gè)庫(kù)你可
- 下一篇
python代碼如何加中文注釋?zhuān)?/h2>
簡(jiǎn)介Python注釋的具體用法:1.單行注釋Python編程語(yǔ)言的單行注釋常以#開(kāi)頭,單行注釋可以作為單獨(dú)的一行放在被注釋代碼行之上,也可以放在語(yǔ)句或者表達(dá)式之后。示例:# -*- coding: UTF-8 -*-print (hello&