垃圾回收算法通常都有個(gè)階段要暫停所有線程對(duì)內(nèi)存對(duì)象引用關(guān)系網(wǎng)絡(luò)的更新,這個(gè)機(jī)制稱為Safepoint。所有線程達(dá)到Safepoint所需要的時(shí)間對(duì)垃圾回收的總體暫停時(shí)間有很大影響。
要暫停一個(gè)線程的執(zhí)行有兩個(gè)途徑,一個(gè)是協(xié)作式的(非搶占式),一個(gè)是搶占式的。用戶態(tài)的線程可以被處于核心態(tài)的時(shí)鐘中斷搶占(從而完成調(diào)度)。但是用戶態(tài)的線程怎么能被同樣處于用戶態(tài)的垃圾回收線程搶占呢?所有通常只能采用協(xié)作式的方式,讓線程自己暫停。
基本的思想是設(shè)置一個(gè)全局變量作為暫停標(biāo)記,每個(gè)線程時(shí)不時(shí)的去檢查一下這個(gè)標(biāo)記看是不是需要暫停。當(dāng)然越快停下越好,但是也不能時(shí)時(shí)刻刻檢查浪費(fèi)CPU。
JVM運(yùn)行的幾種類型的代碼有自己的檢查這個(gè)標(biāo)記的方式:
(1)對(duì)于正常解釋執(zhí)行的Java字節(jié)碼。此時(shí)解析器會(huì)在執(zhí)行每條指令前做檢查(可以做些優(yōu)化)。
(2)JNI代碼。如果JNI代碼不和JVM交互,實(shí)際上可以不用暫停繼續(xù)執(zhí)行,但是一旦調(diào)用JVM代碼就會(huì)檢查暫停標(biāo)記。
(3)JIT編譯出來(lái)的機(jī)器碼。此時(shí)會(huì)在編譯的代碼中插入檢查暫停標(biāo)記的代碼,比如在方法調(diào)用或者循環(huán)語(yǔ)句中。
HotSpot JVM以一種比較特殊的方式來(lái)檢查暫停標(biāo)記。這個(gè)標(biāo)記放在一個(gè)特殊的內(nèi)存位置。當(dāng)需要暫停的時(shí)候,JVM把這塊內(nèi)存unmap掉,從而觸發(fā)中斷,JVM處理這個(gè)中斷,然后把線程掛起。
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html
Go的做法是類似的,一開(kāi)始只能在函數(shù)調(diào)用時(shí)檢查暫停標(biāo)記,后來(lái)增加了在循環(huán)中檢查。
https://github.com/golang/go/issues/10958
但是Go對(duì)于Safepoint檢查帶來(lái)的性能影響不太滿意,所以計(jì)劃實(shí)現(xiàn)搶占式方式。 其實(shí)“用戶態(tài)的線程怎么能被同樣處于用戶態(tài)的垃圾回收線程搶占”還是有可能的,可以利用信號(hào)來(lái)實(shí)現(xiàn),具體怎么做還不是太清楚。
https://github.com/golang/go/issues/24543