1、虛擬機運行模式
java是一種解釋性編程語言空盼,在Hotspot實現(xiàn)中,提供了解釋器和即時編譯器揽趾,即時編譯器能將熱點代碼編譯為效率更高的機器代碼,以提升執(zhí)行效率篱瞎,加快系統(tǒng)運行速度苟呐。
hotspot運行模式配置:
- 解釋模式:可通過 -Xint 選項指定俐筋,讓 JVM 以解釋模式運行 Java 程序牵素。
- 編譯模式:可通過 -Xcomp 選項指定,讓 JVM 以編譯模式運行 Java 程序澄者。
- 混合模式:可通過 -Xmixed 選項指定,讓 JVM 以解釋+編譯模式運行 Java 程序粱挡,這也是 Hotspot 的默認模式.
2、解釋器
系統(tǒng)啟動時榕堰,解釋器按照預定義的規(guī)則逆屡,為所有字節(jié)碼分別創(chuàng)建能夠在具體計算機平臺上運行的機器碼魏蔗,并存放在特定位置勇蝙。當運行時環(huán)境需要解釋字節(jié)碼時味混,就到指定位置取出相應的code,直接在機器上運行诫惭。
3翁锡、即時編譯
解釋性語言的優(yōu)點是可移植性強,可以放在任何有解釋器的機器上運行夕土,但其運行性能較差馆衔。原因是解釋器需要實時將指令解釋為機器碼運行瘟判,而且解釋器未對指令做更好的優(yōu)化。
編譯器有足夠的上下文信息角溃、統(tǒng)計信息等拷获,可以對代碼進行更好的優(yōu)化。
3.1减细、編譯器類型
編譯器類型有client(C1編譯器)和server(C2編譯器)兩種類型匆瓜。兩種編譯器的主要差別是編譯代碼的時機不同。client編譯器在代碼執(zhí)行的開始階段未蝌,其編譯速度比server編譯器塊驮吱,而編譯的代碼比server編譯的多。server編譯器在編譯的時候會更好地進行優(yōu)化萧吠。
一種列外是分層編譯左冬,在虛擬機啟動時由client快速編譯,隨著代碼變熱后使用server編譯器重新編譯纸型,以提高系統(tǒng)的整體性能拇砰。
編譯器版本:
- -client:32位client編譯器
- -server:32位server編譯器
- -d64:64位server編譯器
- -XX:+TieredCompilation:分層編譯器,其使用server編譯器绊袋。
3.2毕匀、即時編譯器版本選擇
- 32位系統(tǒng):必須選用32位的JVM,即client編譯器癌别;
- 64位系統(tǒng):可選32位或64位皂岔。如果內(nèi)存小于3GB,32位編譯器更快展姐,占用內(nèi)存更少躁垛。因為虛擬機指針只有32位,所占內(nèi)存空間少圾笨,而且操作代價也少于64位的指針教馆。32位編譯器的缺點是內(nèi)存最多只能為4GB。而且還包括堆擂达、永久帶土铺、本地代碼及JVM所使用的本地內(nèi)存。另外板鬓,32位編譯器無法使用64位CPU的寄存器悲敷,無法進行寄存器相關的優(yōu)化,故大量使用long或double變量的應用會比較慢俭令。在 32 位 JVM 上運行的程序后德、只要與 32 位尋址空間吻合,無論機器是 32 位還是 64 位抄腔,都要比在類似配置的 64 位 JVM 上運行時快 5 %到 20 %瓢湃。
3.3理张、代碼緩存
虛擬機編譯時,會在代碼緩存中保留編譯之后的匯編語言指令集绵患。代碼緩存的大小固定雾叭,一旦填滿,虛擬機就不能編譯更多代碼了落蝙。故若代碼緩存過小拷况,只有少部分熱點代碼被編譯執(zhí)行,其他的則沒有掘殴,最終會導致大部分代碼都是解釋運行赚瘦,系統(tǒng)性能較差。
以上情況在client或分層編譯的情況下很常見奏寨。在使用常規(guī)server編譯器時起意,因通常只有少量類會被編譯,所以代碼緩存不太可能被填滿病瞳。而在client或分層編譯時揽咕,可被編譯的類可能會非常多。
參數(shù) | 說明 |
---|---|
-XX:CodeCacheExpansionSize | 配置CodeCache空間擴展大小的參數(shù) |
-XX:InitialCodeCacheSize | 配置CodeCache空間的初始值 |
-XX:ReservedCodeCache | 配置CodeCache空間的最大值 |
-XX:PrintCodeCache | 退出時輸出CodeCache信息 |
3.4套菜、編譯閾值
觸發(fā)代碼編譯的主要因素是代碼執(zhí)行的頻度亲善,當執(zhí)行達到一定次數(shù),且到達編譯閾值逗柴,編譯器就可獲得足夠的信息進行代碼編譯了蛹头。
編譯時基于兩種計數(shù)器:方法調(diào)用計數(shù)器和方法中循環(huán)回邊計數(shù)器∠纺纾回邊為循環(huán)執(zhí)行的次數(shù)渣蜗。
虛擬機在執(zhí)行某個方法時,會檢查方法中的兩種計數(shù)器總數(shù)旷祸,然后判斷是否需要編譯耕拷。需要編譯則將該方法放入編譯隊列,進行標準編譯托享。
標準編譯由 -XX:CompileThreshold=N標志觸發(fā)骚烧,N=回邊計數(shù)器+方法調(diào)用次數(shù)。使用client編譯器時闰围,N默認值為1500赃绊,使用server編譯器時為10000。
實際上每種計數(shù)器的值都會周期性減少辫诅,計數(shù)器只是方法或循環(huán)最新熱度的度量凭戴。
3.5涧狮、編譯線程
編譯隊列中的任務是被后臺多個線程處理的炕矮。編譯隊列并不遵循先進先出的原則么夫,而是根據(jù)調(diào)用次數(shù)的多少作為優(yōu)先級。故當在出現(xiàn)開始執(zhí)行并有大量代碼需要編譯時肤视,最重要档痪、執(zhí)行次數(shù)最多的代碼會被優(yōu)先編譯。
當使用 client 編譯器時邢滑, JVM 會開啟一個編譯線程腐螟;使用 Server編譯器時,JVM 會開啟兩個這樣的線程困后。當啟用分層編譯時乐纸, JVM 默認開啟多個 client 和 server 線程,線程數(shù)依據(jù)一個略復雜的等式而定摇予,包括目標平臺 CPU 數(shù)取雙對數(shù)之后的數(shù)值汽绢。
編譯器的線程數(shù)( 3 種編譯器都是如此)可通過 -xx:CICompilerCount=N 標志來設置。這是JVM 處理隊列的線程總數(shù)侧戴;對分層編譯來說宁昭,其中三分之一(至少一個)將用來處理client 編譯器隊列,其余的線程(至少一個)用來處理 server編譯器隊列酗宋。
3.6积仗、內(nèi)聯(lián)
編譯器所做的最重要的優(yōu)化方法內(nèi)聯(lián)。面向?qū)ο笤O計都會有很多getter和setter方法蜕猫,此類方法調(diào)用開銷很大寂曹,特別是相對于方法的代碼量而言。當前的JVM都會用內(nèi)聯(lián)代碼的方式執(zhí)行這些方法回右。
內(nèi)聯(lián)是默認開啟的稀颁,可通過 -xx:-InLine關閉,然而其對性能影響巨大楣黍,最好不要關閉匾灶。
方法是否內(nèi)聯(lián)取決干它有多熱以及它的大小。 JVM 依據(jù)內(nèi)部計算來判定方法是否是熱點(譬如租漂,調(diào)用很頻繁)阶女;是否是熱點并不直接與任何調(diào)優(yōu)參數(shù)相關。如果方法因調(diào)用頻繁而可以內(nèi)聯(lián)哩治,那只有在它的字節(jié)碼小于 325 字節(jié)時(或 -XX:MaxFreqInlineSize = N 所設定的任意值)才會內(nèi)聯(lián)秃踩。否則,只有方法很小時业筏,即小于 35 字節(jié)(或 -xx : MaxlnlineSize = N 所設定的任意值)時才會內(nèi)聯(lián)憔杨。
有時你會看到增加MaxInlineSize 的值以便內(nèi)聯(lián)更多方法的建議。兩者之間常被忽略的是蒜胖,MaxInlineSize 超過35意味著第一次調(diào)用方法是就會被內(nèi)聯(lián)消别。然而抛蚤,方法只有經(jīng)常被調(diào)用時—在這種情況下它的性能會受更大影響― 最終才值得內(nèi)聯(lián)(假定它的大小小于 325 字節(jié))。否則寻狂,MaxInlineSize調(diào)用的最終結(jié)果就是減少了熱身測試所需要的時間岁经,但不太可能對長期運行的程序產(chǎn)生重大影響。
3.7蛇券、逃逸分析
如果開啟逃逸分析(-XX:DoEscapeAnalysist缀壤,默認為true),server編譯器會執(zhí)行一些非常激進的優(yōu)化措施纠亚。
4塘慕、分層編譯級別
當使用分層編譯時,編譯日志中會輸出代碼所編譯的分層級別蒂胞。
共5種編譯級別:
- 0級:CompLevel_none苍糠,采用解釋器解釋執(zhí)行,不采集性能監(jiān)控數(shù)據(jù)啤誊,可以升級到1級岳瞭;
- 1級:CompLevel_simple,采用C1編譯器蚊锹,會把熱點代碼迅速的編譯成本地代碼瞳筏,如果需要可以采用性能數(shù)據(jù);
- 2級:CompLevel_limited_profile牡昆,采用C2編譯器姚炕,進行更好的優(yōu)化,甚至可能根據(jù)第1級采集的性能數(shù)據(jù)采取激進的優(yōu)化措施丢烘。
- 3級:CompLevel_full_profile柱宦,采用C1編譯器,采集性能數(shù)據(jù)進行優(yōu)化措施播瞳;
- 4級:CompLevel_full_optimization掸刊,采用C2編譯器,進行完全的優(yōu)化赢乓;
多數(shù)方法第一次編譯級別是3忧侧,即完全C1編譯,如果方法運行的足夠頻繁牌芋,它就會編譯成4蚓炬。
影響編譯策略的因素有兩個:
- C2 隊列的長度決定了下一個等級.據(jù)觀察,第 2 級比第 3 級快約 30 % 躺屁,因此我們需要將一個 Java 方法花費在第 3 級上的時間盡可能地最小化.所以肯夏,若 C2隊列很長,直接選擇第 3 級會導致排隊,直到所提交的 C2 編譯請求通歷整個隊列.因此此時較為明智的做法是先使用第 2 級驯击,待 C2 負載回落烁兰,再啟動第 3 級重新編譯并開始收集性能數(shù)據(jù)。
- C1 隊列的長度用來動態(tài)調(diào)整閾值余耽,從而在編譯器過載時引入額外的過濾.
如果server編譯器隊列滿了,就會從 server 隊列中取出方法苹熏,以級別 2 進行編譯碟贾,在這個級別上, C1 編譯器使用方法調(diào)用計數(shù)器和回邊計數(shù)器(但不需要性能分析的反饋信息)轨域。這使得方法編譯得更快袱耽,而方法也將在 C1 編譯器收集分析信息、之后被編譯為級別 3 干发,最終當 server 編譯器隊列不太忙的時候被編譯為級別 4 朱巨。
另一方面,如果client 編譯器全忙枉长,原本排程在級別 3 編譯的方法就既可以等待級別 3 編譯冀续,也適合進行級別 4 的編譯。在這種情況下必峰,方法編譯會很快轉(zhuǎn)到級別 2 洪唐,然后由級別 2 轉(zhuǎn)到級別 4 。
那些不太重要的方法可以從級別 2 或級別3 開始編譯吼蚁,但隨后會因為它們的重要性沒那么高而轉(zhuǎn)為級別 1 凭需。另外,如果 Server 編譯器出于某些原因無法編譯代碼肝匆,也會轉(zhuǎn)為級別 1粒蜈。當然,代碼在逆編譯時會轉(zhuǎn)為級別0 旗国。
有些標志可以控制某些級別轉(zhuǎn)換行為枯怖,但調(diào)優(yōu)能夠得到很樂觀的結(jié)果。當方法按期望的順序能曾,即級別 0 —>級別 3 —>級別 4 編譯時嫁怀,性能可以達到最優(yōu)。如果方法經(jīng)常被編譯為級別 2 借浊,并且還額外有可用的 CPU 周期塘淑,那就可以考慮增加編譯器的線程數(shù),從而減少 server編譯器隊列的長度蚂斤。如果沒有額外可用的 CPU 周期存捺,那你唯一能做的就是盡力減小應用的大小。