Go垃圾回收之六:掃描灰色對(duì)象
都云作者癡,誰解其中味?
從根對(duì)象的收集來看,會(huì)將全局變量、析構(gòu)器、所有協(xié)程的棧進(jìn)行掃描。從而標(biāo)記目前還在使用的內(nèi)存對(duì)象。 下一步是要從這些標(biāo)記為灰色的內(nèi)存對(duì)象出發(fā), 進(jìn)一步標(biāo)記整個(gè)堆內(nèi)存中活著的對(duì)象。
在之前進(jìn)行根對(duì)象掃描的時(shí)候,會(huì)將標(biāo)記的對(duì)象放入到本地隊(duì)列中,而如果本地隊(duì)列放不下,就放入全局隊(duì)列中。這種設(shè)計(jì)最大限度的防止了使用鎖,而在本地緩存的隊(duì)列可以被邏輯處理器P無鎖訪問。
在進(jìn)行掃描時(shí),使用相同的原理,首先消費(fèi)本地隊(duì)列中找到的標(biāo)記對(duì)象,如果本地隊(duì)列為空后,則加鎖獲取全局隊(duì)列。

在標(biāo)記期間、會(huì)循環(huán)往復(fù)的從本地標(biāo)記隊(duì)列獲取到灰色對(duì)象,灰色對(duì)象掃描到的白色對(duì)象仍然會(huì)放入標(biāo)記隊(duì)列中,而如果掃描到已經(jīng)被標(biāo)記的對(duì)象,則進(jìn)行忽略,一直到隊(duì)列中任務(wù)為空為止。
for !(preemptible && gp.preempt) {
// 從本地標(biāo)記隊(duì)列中獲取對(duì)象, 獲取不到則從全局標(biāo)記隊(duì)列獲取
b := gcw.tryGetFast()
if b == 0 {
// 阻塞獲取
b = gcw.tryGet()
}
// 掃描獲取到的對(duì)象
scanobject(b, gcw)
...
}對(duì)象的掃描過程位于scanobject函數(shù)中。之前介紹過,堆上的任意一個(gè)指針能夠找到其對(duì)象所在span中的位置,并且對(duì)象有沒有被掃描,我們也可以通過gcmarkBits標(biāo)志得出。但現(xiàn)在面對(duì)的問題是需要對(duì)整個(gè)對(duì)象進(jìn)行掃描,查看對(duì)象中是否含有指針。這仍然依靠的是在分配時(shí)就記錄了對(duì)象中是否包含指針等信息。之前介紹過,heapArena 包含了 整個(gè)64M大小 的Arena元數(shù)據(jù)。
arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena其中包含一個(gè)重要的字段,用位圖的形式記錄了每一個(gè)指針大小(8byte)的內(nèi)存信息。每一個(gè)指針大小的內(nèi)存都會(huì)有兩個(gè)bit分別表示是否應(yīng)該繼續(xù)掃描和是否包含指針。
bitmap [heapArenaBitmapBytes]byte如下所示, bitmap代表一個(gè)位圖,其一個(gè)byte大小的空間中,對(duì)應(yīng)了虛擬內(nèi)存4個(gè)指針大小的空間。
bitmap中前4位為掃描位,后4位為指針位。分別對(duì)應(yīng)了指定的指針大小空間是否需要繼續(xù)進(jìn)行掃描以及是否包含指針。

例如,對(duì)于一個(gè)結(jié)構(gòu)體obj, 當(dāng)我們知道其前2個(gè)字段為指針,后面的字段以及不包含指針以后,那么后面的字段就不再需要掃描。因此,掃描位可以加速對(duì)象的掃描,避免掃描無用的字段。
type obj struct{
*int a
*T b
intc
float d
}當(dāng)發(fā)現(xiàn)需要繼續(xù)掃描并且發(fā)現(xiàn)了當(dāng)前有指針時(shí),就需要取出指針的值,并對(duì)其進(jìn)行掃描,如果發(fā)現(xiàn)引用的是堆中的白色對(duì)象(即還沒有被標(biāo)記),則標(biāo)記將該對(duì)象(表明此時(shí)已經(jīng)為灰色) 并將該對(duì)象放入本地任務(wù)隊(duì)列中。

標(biāo)記終止階段
當(dāng)完成并發(fā)標(biāo)記階段所有灰色對(duì)象的掃描和標(biāo)記,則進(jìn)入到標(biāo)記終止階段。標(biāo)記終止階段會(huì)再次進(jìn)入STW,標(biāo)記終止階段主要完成一些指標(biāo)例如用時(shí)的統(tǒng)計(jì)、統(tǒng)計(jì)強(qiáng)制開始GC的次數(shù)、更新下一次觸發(fā)gc需要達(dá)到的目標(biāo)、關(guān)閉寫屏障、并喚醒后臺(tái)清掃的協(xié)程,開始下一階段的清掃工作。
在標(biāo)記終止階段重要的任務(wù)是計(jì)算下一次觸發(fā)垃圾回收時(shí)需要達(dá)到的堆目標(biāo),這叫做垃圾回收的調(diào)步算法,下一小節(jié)中將會(huì)詳細(xì)介紹。
推薦閱讀
