本章主要是對上一篇文章講的垃圾回收機(jī)制的擴(kuò)展,垃圾回收其實(shí)本身是有很多可以優(yōu)化的點(diǎn)的,本章就進(jìn)行對這些優(yōu)化點(diǎn)進(jìn)行介紹。
1.GC Roots遍歷提升效率
以往做法
當(dāng)垃圾回收器線程進(jìn)行GC時, 第一步需要 「找到GC Roots」 ;第二步通過GC Roots進(jìn)行 「遍歷堆中引用GC Roots的對象形成引用鏈」 ;第三步,將不在引用鏈中的對象標(biāo)記進(jìn)行 「標(biāo)記」 (需要回收的對象),或者 「標(biāo)記」 引用鏈中的對象(需要復(fù)制,整理的對象),具體標(biāo)記哪種對象根據(jù)堆中的分代內(nèi)存不同和采用的垃圾回收算法來確定。
可優(yōu)化地方以及優(yōu)化原理
上述過程第二步中遍歷堆中引用GC Roots的對象,這部分隨著堆內(nèi)存的越來越大需要的時間也會逐步增長。如果能夠提前知道堆中哪部分內(nèi)存是引用,來判斷是否引用GC Roots這樣效率是不是會更高一些。
沒錯,因?yàn)橹爸v過從Exact VM開始就已經(jīng)采用了準(zhǔn)確式內(nèi)存管理即知道哪部分內(nèi)存是引用;而且在即時編譯的過程中我也會知道棧中或者寄存器里哪部分內(nèi)存是引用。這個時候我用一個數(shù)據(jù)結(jié)構(gòu)來存儲這些信息,在第二步中就不需要遍歷整個堆了,只需要遍歷沒有標(biāo)識引用內(nèi)存的地方(也就是剛才數(shù)據(jù)結(jié)構(gòu)中沒有存儲的信息)。
在HotSpot中使用OopMap這個數(shù)據(jù)結(jié)構(gòu)來存儲這信息,也就是可以顯著提高GC Roots遍歷的效率,但是在什么位置放這些信息呢?
2.提升了GC Roots遍歷效率卻不知道怎么安插?
前面提到過通過一個OopMap數(shù)據(jù)結(jié)構(gòu)能夠提升遍歷效率,但是OopMap中的數(shù)據(jù)在不同的地方內(nèi)容是不一樣的(比如每個方法里面我的局部變量表里面的內(nèi)容可能是不一樣的),所以 我為每個指令附近都放一個OopMap 。
?
等等,這樣未免也太浪費(fèi)內(nèi)存了吧~。
?
沒錯,所以我們得先辦法把它放到合適的地方!嗯沒錯,我想想: **「這個數(shù)據(jù)結(jié)構(gòu)的出現(xiàn)是為了優(yōu)化GC第二步的效率出現(xiàn)的,也就是說只有GC時在放這些數(shù)據(jù)就行了~。思路找到了,但是什么時候發(fā)生GC呢?
發(fā)生GC這個時間我不能確定,但是我可以確定的是它遍歷堆中內(nèi)存的時候必須要進(jìn)行STW【否則如果在標(biāo)記的過程中堆中引用發(fā)生變化就會導(dǎo)致標(biāo)記結(jié)果出錯】(2.1中講解),我指定只有代碼中執(zhí)行執(zhí)行到某個地方才可以進(jìn)行STW這樣我就可以間接的實(shí)現(xiàn)我的目的」** 。
也就是說當(dāng)GC發(fā)生時,只有執(zhí)行到某個地方才會進(jìn)行STW,然后我在這個地方附近放上這么一個OopMap的數(shù)據(jù)結(jié)構(gòu),然后加快第二步的效率。
「這個某個地方其實(shí)名字叫做“safePoint”」 ,顧名思義安全點(diǎn),只有代碼執(zhí)行到安全點(diǎn)附近才可以進(jìn)行STW垃圾收集,而只要將OopMap安插到安全點(diǎn)附近就行。
2.1為什么需要STW?
上面提到過:
?
【否則如果在標(biāo)記的過程中堆中引用發(fā)生變化就會導(dǎo)致標(biāo)記結(jié)果出錯】
?
一,三色標(biāo)記法
接下來用三色標(biāo)記法進(jìn)行解釋如果沒有STW會發(fā)生什么情況:一,先解釋三色標(biāo)記法:
二,沒有STW出現(xiàn)的情況
在這里插入圖片描述
三,解決方案
上面那種異常情況必須同時滿足兩個條件:1.灰色對象不引用白色對象 2.黑色讀寫引用白色對象
因此,只要讓其中一個條件不滿足即可,因此出現(xiàn)了兩種解決方案:1.增量更新:這種方案是讓第二個條件不滿足,即當(dāng)黑色對象引用白色對象時,將這個黑色對象保存下來,等掃描結(jié)束后,再次取出黑色對象進(jìn)行掃描,可以簡單理解為如果黑色對象引用了百世對象就會被標(biāo)記為灰色。
2.原始快照:當(dāng)灰色對象刪除白色對象的引用時,將這個灰色對象記錄下來,等到掃描結(jié)束后,在對這些灰色對象為根進(jìn)行掃描,簡單理解為:不管是否刪除與否都會按照第一次剛開始的引用關(guān)系圖進(jìn)行掃描。
?
CMS垃圾回收器采用增量更新來進(jìn)行并發(fā)標(biāo)記,G1,Shenandoah采用原始快照
?
3.safePoint我又該放到哪里?
safePoint上面解釋過了,但是我該在哪里放置safePoint呢?放的多了會導(dǎo)致GC收集過于頻繁增加運(yùn)行時內(nèi)存壓力,放的少了又會因?yàn)槎阎胁粩嘣黾邮褂玫膬?nèi)存而沒有及時回收堆里面內(nèi)存導(dǎo)致垃圾收集器等待時間過長。
這樣,我定義一個規(guī)則,只有這種**“會讓程序長時間運(yùn)行的指令”**特征我才會進(jìn)行安插safePoint,但是這個特征“長時間”并沒有具體的定義,但是卻有“指令序列復(fù)用”這樣的含義。比如方法調(diào)用,循環(huán)調(diào)整,異常跳轉(zhuǎn)這些,只有這些指令附近才會安插safePoint。
safePoint位置選好了,但是上個問題說過執(zhí)行到safePoint中需要進(jìn)行STW,發(fā)生GC時,我該如何快速跑到safePoint附近進(jìn)行STW?還有我這個STW該怎么實(shí)現(xiàn)呢?
4.如何實(shí)現(xiàn)STW?
首先解釋為什么叫做STW,全稱“Stop the Word”,因?yàn)?「通過GC Roots遍歷堆中內(nèi)存的過程其內(nèi)存里面的引用關(guān)系不能發(fā)生變化」 ,所以需要暫停所有的用戶線程操作來保障Gc Roots形成的引用鏈?zhǔn)钦_的即待會標(biāo)記過程不會出錯。
讓所有線程都暫停,這個“看起來復(fù)雜其實(shí)并不簡單”的操作其實(shí)有兩種方式處理:一,搶先式中斷:
?
垃圾收集器收集時,系統(tǒng)將所有用戶線程都中斷。當(dāng)發(fā)現(xiàn)不在safePoint附近的線程時先讓他恢復(fù)運(yùn)行直至跑到safePoint附近。這種方式現(xiàn)在幾乎沒有虛擬機(jī)采用這種方式來響應(yīng)GC。
?
二,主動式中斷:
?
我不直接對我的用戶線程操作,當(dāng)發(fā)生GC時,我給用戶線程設(shè)立個標(biāo)志位,用戶線程執(zhí)行的時候不斷輪詢這個標(biāo)志位,如果輪詢到了那么我將自己中斷我自己的運(yùn)行,由于這種方式是輪詢到就立馬進(jìn)行掛起所以將輪詢的地方和safePoint的地方重合。
?
優(yōu)化
“不斷輪詢標(biāo)志位”這句話聽起來就很耗時哈哈,那么再虛擬機(jī)中是怎么優(yōu)化的呢?還有輪詢之后的操作我自己掛起我自己這個又是怎么實(shí)現(xiàn)的?
等等,我不放到下一個問題里面講了,直接一遍過:
輪詢標(biāo)志位這個操作其實(shí)就是一條匯編指令,(對于匯編和JAVA是什么關(guān)系,之前也有講到過,辛苦翻閱前面文章~) 這條匯編指令的意思就是當(dāng)我輪詢到需要中斷線程的標(biāo)志位的時候:我會將其中一個內(nèi)存頁設(shè)置為不可讀,這會導(dǎo)致產(chǎn)生一個自陷異常信號,異常處理器中接受到后進(jìn)行主動中斷操作。
-
編程
+關(guān)注
關(guān)注
88文章
3628瀏覽量
93811 -
GC
+關(guān)注
關(guān)注
0文章
9瀏覽量
17093 -
JVM
+關(guān)注
關(guān)注
0文章
158瀏覽量
12238
發(fā)布評論請先 登錄
相關(guān)推薦
評論