java安全点_关于OopMap、SafePoint(安全点)以及安全区域
1.OopMap
之前我們提到,在正式的GC之前總是需要進行可達性分析來查找內存中所有存活的對象,以便GC能夠正確的回收已經死亡的對象。那么對于一個十分復雜的系統,每次GC的時候都要遍歷所有的引用肯定是不現實的。因為在可達性分析的時候,需要進行Stop The World,程序中的線程需要停止來配合可達性分析。就好像是你女朋友在打掃衛生的時候(什么,你還沒有女朋友?這還能難道程序員了?new 一個啊!),肯定不會讓你走來走去的。所以,你肯定在內心里也希望你女朋友打掃衛生快一點,因為你的膀胱已經快要爆炸了。對于程序來說有也一樣,也希望GC的時候快一點,以便讓程序高效地完成工作。
所以,每次直接遍歷整個引用鏈肯定是不現實的。 為了應對這種尷尬的問題,最早有保守式GC和后來的準確式GC。這里準確式GC就會提到一個OopMap,用來保存類型的映射表。
保守式GC
在進行GC的時候,會從一些已知的位置(GC Roots)開始掃描內存,掃描到一個數字就判斷他是不是可能是指向GC堆中的一個指針(這里會涉及上下邊界檢查(GC堆的上下界是已知的)、對齊檢查(通常分配空間的時候會有對齊要求,假如說是4字節對齊,那么不能被4整除的數字就肯定不是指針),之類的。)。然后一直遞歸的掃描下去,最后完成可達性分析。這種模糊的判斷方法因為無法準確判斷一個位置上是否是真的指向GC堆中的指針,所以被命名為保守式GC。這種可達性分析的方式因為不需要準確的判斷出一個指針,所以效率快,但是也正因為這種特點,他存在下面兩個明顯的缺點:
因為是模糊的檢查,所以對于一些已經死掉的對象,很可能會被誤認為仍有地方引用他們,GC也就自然不會回收他們,從而引起了無用的內存占用,就是典型的占著茅坑不拉屎,造成資源浪費。
由于不知道疑似指針是否真的是指針,所以它們的值都不能改寫;移動對象就意味著要修正指針。換言之,對象就不可移動了。有一種辦法可以在使用保守式GC的同時支持對象的移動,那就是增加一個間接層,不直接通過指針來實現引用,而是添加一層“句柄”(handle)在中間,所有引用先指到一個句柄表里,再從句柄表找到實際對象。這樣,要移動對象的話,只要修改句柄表里的內容即可。但是這樣的話引用的訪問速度就降低了。Sun JDK的Classic VM用過這種全handle的設計,但效果實在算不上好。
2.準確式GC
與保守式GC相對的就是準確式GC,何為準確式GC?就是我們準確的知道,某個位置上面是否是指針,對于java來說,就是知道對于某個位置上的數據是什么類型的,這樣就可以判斷出所有的位置上的數據是不是指向GC堆的引用,包括棧和寄存器里的數據。
網上看了下說是實現這種要求的方法有好幾種,但是在java中實現的方式是:從我外部記錄下類型信息,存成映射表,在HotSpot中把這種映射表稱之為OopMap,不同的虛擬機名稱可能不一樣。
實現這種功能,需要虛擬機的解釋器和JIT編譯器支持,由他們來生成OopMap。生成這樣的映射表一般有兩種方式:
每次都遍歷原始的映射表,循環的一個個偏移量掃描過去;這種用法也叫“解釋式”;
為每個映射表生成一塊定制的掃描代碼(想像掃描映射表的循環被展開的樣子),以后每次要用映射表就直接執行生成的掃描代碼;這種用法也叫“編譯式”。
總而言之,GC開始的時候,就通過OopMap這樣的一個映射表知道,在對象內的什么偏移量上是什么類型的數據,而且特定的位置記錄下棧和寄存器中哪些位置是引用。
2.SafePoint(安全點)
上面講到了為了快點進行可達性的分析,使用了一個引用類型的映射表,可以快速的知道對象內或者棧和寄存器中哪些位置是引用了。
但是隨著而來的又有一個問題,就是在方法執行的過程中, 可能會導致引用關系發生變化,那么保存的OopMap就要隨著變化。如果每次引用關系發生了變化都要去修改OopMap的話,這又是一件成本很高的事情。所以這里就引入了安全點的概念。
什么是安全點?OopMap的作用是為了在GC的時候,快速進行可達性分析,所以OopMap并不需要一發生改變就去更新這個映射表。只要這個更新在GC發生之前就可以了。所以OopMap只需要在預先選定的一些位置上記錄變化的OopMap就行了。這些特定的點就是SafePoint(安全點)。由此也可以知道,程序并不是在所有的位置上都可以進行GC的,只有在達到這樣的安全點才能暫停下來進行GC。
既然安全點決定了GC的時機,那么安全點的選擇就至為重要了。安全點太少,會讓GC等待的時間太長,太多會浪費性能。所以安全點的選擇是以程序“是否具有讓程序長時間執行的特征”為標準的(這句話是從書上看來的,不知道作者自己能不能看明白這話啥意思,反正我是看不懂),所以我們這里了解一下結果就行了。一般會在如下幾個位置選擇安全點:
循環的末尾
方法臨返回前 / 調用方法的call指令后
可能拋異常的位置
還有一個需要考慮的問題就是,如何讓程序在要進行GC的時候都跑到最近的安全點上停頓下來。這里有兩種方案:
搶斷式中斷
搶斷式中斷就是在GC的時候,讓所有的線程都中斷,如果這些線程中發現中斷地方不在安全點上的,就恢復線程,讓他們重新跑起來,直到跑到安全點上。(現在幾乎沒有虛擬機采用這種方式,原因不詳)
主動式中斷
主動式中斷在GC的時候,不會主動去中斷線程,僅僅是設置一個標志,當程序運行到安全點時就去輪訓該位置,發現該位置被設置為真時就自己中斷掛起。所以輪訓標志的地方是和安全點重合的,另外創建對象需要分配內存的地方也需要輪詢該位置。
3.安全區域
安全點的使用似乎解決了OopMap計算的效率的問題,但是這里還有一個問題。安全點需要程序自己跑過去,那么對于那些已經停在路邊休息或者看風景的程序(比如那些處在Sleep或者Blocked狀態的線程),他們可能并不會在很短的時間內跑到安全點去。所以這里為了解決這個問題,又引入了安全區域的概念。
安全區域很好理解,就是在程序的一段代碼片段中并不會導致引用關系發生變化,也就不用去更新OopMap表了,那么在這段代碼區域內任何地方進行GC都是沒有問題的。這段區域就稱之為安全區域。線程執行的過程中,如果進入到安全區域內,就會標志自己已經進行到安全區域了。那么虛擬機要進行GC的時候,發現該線程已經運行到安全區域,就不會管該線程的死活了。所以,該線程在脫離安全區域的時候,要自己檢查系統是否已經完成了GC或者根節點枚舉(這個跟GC的算法有關系),如果完成了就繼續執行,如果未完成,它就必須等待收到可以安全離開安全區域的Safe Region的信號為止。
總結
以上是生活随笔為你收集整理的java安全点_关于OopMap、SafePoint(安全点)以及安全区域的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux重新安装mysql步骤_Lin
- 下一篇: 删掉java影响什么_java带来的影响