更新時間:2021-06-02 15:18:25 來源:動力節點 瀏覽942次
學習JVM相關的知識,必然繞不開即時編譯器,因為它太重要了。了解了它的基本原理及優化手段,在編程過程中可以讓我們有種打開任督二脈的感覺。比如,很多朋友在面試當中還會遇到這樣的問題:Java是基于編譯執行還是基于解釋執行?當你了解了Java的即時編譯器,不僅能夠輕松回答上述問題,還能如數家珍的講出JVM在即時編譯器上采用的優化技術,而且在實踐過程中更深刻的理解代碼背后的原理。本文便帶大家全面的了解Java即時編譯器。
在部分的商用虛擬機中,比如HotSpot中,Java程序先通過解釋器(Interceptor)進行解釋執行。這也是為什么稱Java是基于解釋執行的原因。但當虛擬機發現某塊代碼或方法運行的特別頻繁,便會將其標記為“熱點代碼”(Hot Spot Code)。
針對熱點代碼,虛擬機會采用各種措施來提升其執行效率,因為執行比較頻繁,如果能夠提升其執行效率,性價比還是比較高的。為此,在運行時,虛擬機會把這些代碼編譯成與本地平臺相關的機器碼,并進行各層次的深度優化。而這些優化操作便是通過編譯器來完成的,也稱作即使編譯器(Just In Time Compiler,簡稱JIT編譯器)。
因此,準確的來說,像HotSpot等虛擬機,Java是基于解釋執行和編譯執行的。下面用一張圖來解釋該過程:
首先,我們需要知道并不是所有的Java虛擬機都采用解釋器與編譯器并存的架構,但許多主流的商用虛擬機(如HotSpot),都同時包含解釋器和編譯器。
既然即時編譯器進行了各層次的優化,那么為什么Java還使用解釋器來“拖累”程序的性能呢?這是因為,解釋器與編譯器兩者各有優勢:當程序需要迅速啟動和執行的時候,解釋器可以首先發揮作用,省去編譯的時間,立即執行。當程序運行環境中內存資源限制較大(如部分嵌入式系統中),可以使用解釋器執行節約內存,反之可以使用編譯執行來提升效率。此外,如果編譯后出現“罕見陷阱”,可以通過逆優化退回到解釋執行。
Java虛擬機運行時,解釋器和即時編譯器能夠相互協作,取長補短。無論采用解釋器進行解釋執行,還是采用即使編譯器進行編譯執行,最終字節碼都需要被轉換為對應平臺的本地機器碼指令。某些服務并不看重啟動時間,而某些服務卻非常看重,這就需要采用解釋器與即時編譯器并存來換取一個平衡點。
我們可以從解釋器和編譯器的編譯時間開銷和編譯空間開銷兩方面進行對比。首先,看編譯的時間開銷。
我們所說的JIT比解釋器快,僅限于對“熱點代碼”編譯之后的代碼執行起來要比解釋器解釋執行的快。通過上圖可以看出,如果是只是單次執行的代碼,JIT編譯比解釋器要多出一步“執行編譯”,因此,只執行一次時,JIT是要比解釋器慢的。只執行一次的代碼通常包括只被調用一次的代碼(比如構造器)、沒有循環的代碼等,此時使用JIT顯然得不償失。
其次,再來看看編譯空間方面的開銷。對一般的Java方法而言,編譯后代碼的大小相對于字節碼,膨脹比達到10倍是很正常的。只有對執行頻繁的代碼才值得編譯,如果把所有代碼都編譯則會顯著增加代碼所占空間,導致“代碼爆炸”。這就是為什么有些JVM不會單一使用JIT編譯,而是選擇用解釋器+JIT編譯器的混合執行引擎。
HotSpot虛擬機為了使用不同的應用場景,內置了兩個即時編譯器:Client Complier和Server Complier,簡稱為C1、C2編譯器,分別用在客戶端和服務端。Client Complier可獲取更高的編譯速度,Server Complier可獲取更好的編譯質量。
JVM Server模式與client模式最主要的差別在于:-server模式啟動時,速度較慢,但是一旦運行起來后,性能將會有很大的提升。原因是:當虛擬機運行在-client模式時,使用的是一個代號為C1的輕量級編譯器,而-server模式啟動的虛擬機采用相對重量級代號為C2的編譯器。C2比C1編譯器編譯的相對徹底,服務起來之后,性能更高。
默認情況下,使用C1還是C2編譯器,要取決于虛擬機運行的模式。HotSpot虛擬機會根據自身版本與宿主機器的硬件性能自動選擇運行模式,用戶也可以使用“-client”或“-server”參數去強制指定虛擬機運行在Client模式或Server模式。
目前主流的HotSpot虛擬機中默認是采用解釋器與其中一個編譯器配合的方式工作,這種配合稱作混合模式(Mixed Mode)。用戶可以使用參數-Xint強制虛擬機運行于“解釋模式”(Interpreted Mode),這時候編譯器完全不介入工作。使用-Xcomp強制虛擬機運行于“編譯模式”(Compiled Mode),這時將優先采用編譯方式執行,但是解釋器仍然要在編譯無法進行的情況下接入執行過程。通過虛擬機java-version命令可以查看當前默認的運行模式。
在上述示例中我們不僅能夠看到采用的模式為mixed mode,還能看到出采用的是Server模式。
上面解釋了JIT編譯器的基本功能,那么它是如何判斷熱點代碼的呢?判斷一段代碼是不是熱點代碼的行為,也叫熱點探測(Hot Spot Detection),通常有兩種方法:基于采樣的熱點探測和基于計數器的熱點探測(HotSpot使用此方式)。
基于采樣的熱點探測(Sample Based Hot Spot Detection):Java虛擬機會周期的對各個線程棧頂進行檢查,如果某些方法經常出現在棧頂,會被定義為“熱點方法”。實現簡單、高效,很容易獲取方法調用關系。但很難確認方法的reduce,容易受到線程阻塞或其他外因擾亂。
基于計數器的熱點探測(Counter Based Hot Spot Detection):為每個方法(甚至是代碼塊)建立計數器,執行次數超過閾值就認為是“熱點方法”。統計結果精確嚴謹,但實現麻煩,不能直接獲取方法的調用關系。
HotSpot虛擬機默認采用基于計數器的熱點探測,有兩種計數器:方法調用計數器和回邊計數器。當計數器數值大于默認閾值或指定閾值時,方法或代碼塊會被編譯成本地代碼。
方法調用計數器,記錄方法調用的次數。Client模式默認閾值是1500次,在Server模式下是10000次,可以通過-XX:CompileThreadhold來設定。如果不做任何設置,方法調用計數器統計的并不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間之內的方法被調用的次數。當超過一定的時間限度,但調用次數仍然未達到閾值,那么該方法的調用計數器就會被減半,稱為方法調用計數器熱度的衰減(Counter Decay),這段時間稱為此方法的統計半衰周期(Counter Half Life Time)。進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數-XX:CounterHalfLifeTime參數設置半衰周期的時間,單位是秒。JIT編譯交互圖如下:
回邊計數器,統計一個方法中循環體代碼執行的次數。在字節碼中遇到控制流向后跳轉的指令稱為“回邊”(Back Edge),建立回邊計數器統的目的是為了觸發OSR編譯。計數器的閾值,HotSpot提供了-XX:BackEdgeThreshold來進行設置,但當前的虛擬機實際上使用了-XX:OnStackReplacePercentage來間接調整閾值,計算公式如下:
與方法計數器不同,回邊計數器沒有計數熱度衰減的過程,因此統計的就是該方法循環執行的絕對次數。當計數器溢出時,它還會把方法計數器的值也調整到溢出狀態,這樣下次再進入該方法的時候就會執行標準編譯過程。
以上就是動力節點小編介紹的"Java編譯器有哪些用法",希望對大家有幫助,如有疑問,請在線咨詢,有專業老師隨時為您服務。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習