這篇文章讓我們收尾GC的具體后續操作。轉載請標明出處:http://www.cnblogs.com/zblade/
3、GC的掃描階段 GCSpropagate


只要處於這個階段,就會分2種情況執行,一個是propagatemark,一個是atomic,讓我們分別看其實現過程。
首先看處於灰色鏈表中一直都有對象的情況,在這步操作當中,是可以分步操作的,整個GC的分步操作,就是在這一步操作中,在每次掃描后,都會返回本次掃描標記的對象的大小之和,再下一個分步執行的時候再繼續執行,而一旦進入atomic函數中,就需要一次性的執行,不能再分步執行了。
來看propagatemark函數是如何實現的:


對於table,如果該表是weak表,則退回到灰色狀態,否則遍歷表的數組和散列表部分進行標記,詳見traversetable函數;
對於func,traverseclosure主要對func中的upval進行標記;
對於thread, 則將其移植到grayagain中,放在atomic中進行處理;
對於proto,對其中的字符串、upvalue、局部變量等進行遍歷標記;
注意,這兒沒有處理string\udata類型數據,這是放在其他部分進行的,不需要進行相關的標記;
4、GC 掃描階段的barrier操作
由於采用分步式增量掃描標記算法,所以會出現在分步操作過程中,新增加的對象與被掃描過的對象之間有引用關系的變化,未來確保黑色對象引用的對象中有白色對象,lua提供了兩種操作設計:
1)標記過程向前走一步 luaC_barrierf
如果新建對象是白色,而它被一個黑色對象引用了,那么將這個新建對象顏色從白色變為灰色;
2)標記過程向后走一步 luaC_barrierback
類似於上,此時將引用的它的黑色對象的顏色從黑色變為灰色,使得其重新被掃描一次


(或許你看出截圖顏色變了,是的,回家了,又是新的編輯器了~)
從define可以看出,只有table需要進行luaC_barrierback,這是由於table本身設計,就是一個table可能會對應N個key或者value,這樣如果新增一個key/value,如果將其置為灰色,然后將其加入gray鏈表中,這樣多個添加會帶來較大的性能。
采用向后,就是將該table對象退回到gray狀態,這樣添加多個,其實質都是只改變該table一次,注意這個gray不是改為gray鏈中,而是將該table加入到grayagain鏈中,在掃描完gray鏈后再掃描grayagain鏈即可。參考源碼即可:


對比向前比較簡單了:直接調用reallymarkoject


5、GC的atomic操作
當gray鏈表中對象都標記完成后,會執行一次atomic操作,注意這個操作是不能被打斷的,所以叫原子操作,參考源碼:


首先處理上一篇文章中提到的對open狀態的upvalues,然后處理一次gray鏈表;
然后處理整個弱表,將lua_State指針指向meta表,然后處理一次gray鏈表
然后處理grayagain鏈表,類似於上
然后處理udata,其處理函數為luaC_separateudata:


注釋很詳細,注意放到tmudata鏈表中后,是在后續操作再集中處理一次;
處理完基本的幾個數據后,atomic會把白色類型切換到下一個GC操作的白色類型,然后修改狀態到回收階段CGSsweepstring, 這兒對sweepstrgc進行了賦初值,是為了下面的字符串定位。
6、GC的回收階段 GCSsweepstring/GCSsweep
首先進入的回收階段是對字符串的處理


雖然是case,但是其實質是一個循環,每次取出散列表中的一個字符串鏈表,進行一次遍歷回收,sweepwholelist最終會調用到sweeplist,等一下給出源碼。
當處理完所有的字符串后,切換到GCSsweep狀態:


關鍵操作是sweeplist,參看其源碼:

代碼中也對前面說的多色標記中的兩種白色的作用做了講解,otherwhite就是本次不可回收的白色,如果處理的對象的白色就是otherwhite,是不會被回收的
7、結束階段 GCSfinalize
這是整個GC的最后階段了,來看看其操作的源碼:


首先處理,是否有前面提到的tmudata鏈表, 其操作函數為GCTM:

注意,udata本身有GC方法,未來確保其GC方法的調用,實在這次GC中調用G方法,但是這個udata本身,是在下一次的GC中才會被回收的。udata的GC調用則是在fasttm中調用TM_GC來實現。
初看也會迷糊怎么循環的,其實結合上面的case中的 if(g->tmudata)可以理解,為什么每次GCTM都會執行 g->tmudata的移動賦值操作。
最終萬事大吉,本次GC流程走完,設置到GCSpause狀態,等待下一次GC調用。
8、GC的進度控制
其實GC的調用,可以分為兩種,一種是自動調用,一個是手動調用
自動調用函數: luaC_checkGC

一般不希望自動GC,可以采用setthreshold,將GCthreshold的值設置為非常大,這樣不回自動觸發GC
手動調用,則設置GC的相關參數 setthreshold:


estimate是對當前內存使用量的一個預估值,gcpause是一個百分比,通過lua_gc可以設置,另一個gc進度的參數是gcstepmul,其主要影響singlestep函數的調用次數,具體原因參看源碼:


整個流程都在注釋中講解了,其中關鍵是lim的設置,然后不斷的調用singlestep, 然后處理GC狀態即可,注意setthreshold是設置的兩次GC之間的時間間隔。由於修改了threshold,對於關閉自動GC的情況,需要再次重新設置關閉自動GC一次。
9、總結
對於lua的GC的原理的探究就到這兒,熟悉一門語言的GC流程后,同理去推導理解其他語言的GC會有很大幫助,同時也可以在平時使用lua的時候,對於GC的一些操作更加知其所以然。大家共勉!