深入解析HotSpot

概念

Java HotSpot虛擬機是Sun用於Java平臺的VM。 它使用許多先進技術爲Java應用程序提供最佳性能,包括最先進的內存模型,垃圾收集器和自適應優化器。

在SUN/Orace JDK中包括兩種風格的VM

  • client mode
  • server mode

默認以client mode啓動。

啓動命令加- server,以server mode啓動。

在CMD裏面輸入 java -version 顯示如下
在這裏插入圖片描述

兩種mode的區別:

client mode

  • 短時間內啓動,運行時,佔用更少內存
  • C1輕量級編譯器,優化較少
  • 適合輕量級程序和桌面程序

server mode

  • 啓動慢,運行時,佔用更大的內存

  • C2重量級編譯器,更徹底的優化

  • 能提供更好的性能,適合生產部署

歷史

SUN的JDK版本從1.3.1開始運用HotSpot虛擬機, 2006年底開源,主要使用C++實現,JNI接口部分用C實現。
HotSpot是較新的Java虛擬機,用來代替JIT(Just in Time),可以大大提高Java運行的性能。
Java原先是把源代碼編譯爲字節碼在虛擬機執行,這樣執行速度較慢。而HotSpot將常用的部分代碼編譯爲本地(原生,native)代碼,這樣顯着提高了性能。
HotSpot JVM 參數可以分爲規則參數(standard options)和非規則參數(non-standard options)。
規則參數相對穩定,在JDK未來的版本里不會有太大的改動。
非規則參數則有因升級JDK而改動的可能。

實現

在這裏插入圖片描述

基礎知識

HotSpot包括一個解釋器和兩個編譯器(client 和 server,二選一的),解釋與編譯混合執行模式,默認啓動解釋執行。

解釋器: 解釋器用來解釋class文件(字節碼),java是解釋語言(書上這麼說的)。

編譯器:java源代碼被編譯器編譯成class文件(字節碼),java字節碼在運行時可以被動態編譯(JIT)成本地代碼(前提是解釋與編譯混合執行模式且虛擬機不是剛啓動時)。

server啓動慢,佔用內存多,執行效率高,適用於服務器端應用;

client啓動快,佔用內存小,執行效率沒有server快,默認情況下不進行動態編譯,適用於桌面應用程序。

由-XX:+RewriteFrequentPairs參數控制 client模式默認關閉,server模式默認開啓

在jre安裝目錄下的lib/i386/jvm.cfg 文件下。

java -version

Java HotSpot™ Client VM (build 14.3-b01, mixed mode, sharing)

mixed mode 解釋與編譯 混合的執行模式 默認使用這種模式

java -Xint -version

Java HotSpot™ Client VM (build 14.3-b01, interpreted mode, sharing)

interpreted 純解釋模式 禁用JIT編譯

java -Xcomp -version

Java HotSpot™ Client VM (build 14.3-b01, compiled mode, sharing)

compiled 純編譯模式(如果方法無法編譯,則回退到解釋模式執行無法編譯的方法)

動態編譯

HotSpot對bytecode的編譯不是在程序運行前編譯的,而是在程序運行過程中編譯的。
HotSpot裏運行着一個監視器(Profile Monitor),用來監視程序的運行狀況。

java字節碼(class文件)是以解釋的方式被加載到虛擬機中(默認啓動時解釋執行)。 程序運行過程中,那一部分運用頻率大,那些對程序的性能影響重要。對程序運行效率影響大的代碼,稱爲熱點(hotspot),HotSpot會把這些熱點動態地編譯成機器碼(native code),同時對機器碼進行優化,從而提高運行效率。對那些較少運行的代碼,HotSpot就不會把他們編譯。

HotSpot對字節碼三層處理

1.不編譯(字節碼加載到虛擬機中時的狀態。也就是當虛擬機執行的時候再編譯)

2.編譯(把字節碼編譯成本地代碼。虛擬機執行的時候已經編譯好了,不要再編譯了)

3.編譯並優化(不但把字節碼編譯成本地代碼,而且還進行了優化)

​ 至於那些程序那些不編譯,那些編譯,那些優化,則是由監視器(Profile Monitor)決定。

爲什麼不靜態編譯?

靜態編譯器通常很難準確預知程序運行過程中究竟什麼部分最需要優化。

函數調用都是很浪費系統時間的,因爲有許多進棧出棧操作。因此有一種優化辦法,就是把原來的函數調用,通過編譯器的編譯,改成非函數調用,把函數代碼直接嵌到調用出,變成順序執行。

​ 面向對象的語言支持多態,靜態編譯無效確定程序調用哪個方法,因爲多態是在程序運行中確定調用哪個方法。

算法實現

1.枚舉根節點

從可達性分析中從GC Roots節點找引用爲例,可作爲GC Roots的節點主要是全局性的引用與執行上下文中,如果要逐個檢查引用,必然消耗時間。

另外可達性分析對執行時間的敏感還體現在GC停頓上,因爲這項分析工作必須在一個能確保一致性的快照中進行——這裏的“一致性”的意思是指整個分析期間整個系統執行系統看起來就行被凍結在某個時間點,不可以出現分析過程中對象引用關係還在不斷變化的情況,該點不滿足的話分析結果的準確性就無法得到保證。這點是導致GC進行時必須暫停所有Java執行線程的其中一個重要原因。
由於目前主流的Java虛擬機都是準確式GC,做一檔執行系統停頓下來之後,並不需要一個不漏的檢查執行上下文和全局的引用位置,虛擬機應當有辦法得知哪些地方存放的是對象的引用。在HotSpot的實現中,是使用一組OopMap的數據結構來達到這個目的的。

2.安全點

在OopMap的協助下,HotSpot可以快速且準確的完成GC Roots的枚舉,但可能導致引用關係變化的指令非常多,如果爲每一條指令都生成OopMap,那將會需要大量的額外空間,這樣GC的空間成本會變的很高。
實際上,HotSpot也的確沒有爲每條指令生成OopMap,只是在特定的位置記錄了這些信息,這些位置被稱爲安全點(SafePoint)。SafePoint的選定既不能太少,以致讓GC等待時間太久,也不能設置的太頻繁以至於增大運行時負荷。所以安全點的設置是以讓程序“是否具有讓程序長時間執行的特徵”爲標準選定的。“長時間執行”最明顯的特徵就是指令序列的複用,例如方法調用、循環跳轉、異常跳轉等,所以具有這些功能的指令纔會產生SafePoint。
對於SafePoint,另一個問題是如何在GC發生時讓所有線程都跑到安全點在停頓下來。這裏有兩種方案:搶先式中斷和主動式中斷。搶先式中斷不需要線程代碼主動配合,當GC發生時,首先把所有線程中斷,如果發現線程中斷的地方不在安全點上,就恢復線程,讓他跑到安全點上。現在幾乎沒有虛擬機實現採用搶先式中斷來暫停線程來響應GC。
而主動式中斷的思想是當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單的設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就自己中斷掛起,輪詢標誌的地方和安全點是重合的另外再加上創建對象需要分配的內存的地方。

3.安全區域

使用安全點似乎已經完美解決了如何進入GC的問題,但實際情況卻並不一定,安全點機制保證了程序執行時,在不太長的時間內就會進入到可進入的GC的安全點。但是程序如果不執行呢?所謂的程序不執行就是沒有分配cpu時間,典型的例子就是線程處於sleep狀態或者blocked狀態,這時候線程無法響應jvm中斷請求,走到安全的地方中斷掛起,jvm顯然不太可能等待線程重新分配cpu時間,對於這種情況,我們使用安全區域來解決。
安全區域是指在一段代碼片段之中,你用關係不會發生變化。在這個區域的任何地方開始GC都是安全的,我們可以把安全區域看做是擴展了的安全點。
當線程執行到安全區域中的代碼時,首先標識自己已經進入了安全區,那樣當在這段時間裏,JVM要發起GC時,就不用管標識自己爲安全區域狀態的線程了。當線程要離開安全區域時,他要檢查系統是否完成了根節點枚舉,如果完成了,那線程就繼續執行,否則他就必須等待,直到收到可以安全離開安全區域的信號爲止。

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章