直譯器與JIT編譯器
早在Java1.0版本的時候,Sun公司釋出了一款名為Sun Classic VM的Java虛擬機器,它同時也是世界上第一款商用Java虛擬機器,在當時這款虛擬機器內部只提供直譯器,用今天的眼光來看待必然是效率低下的,因為如果Java虛擬機器只能夠在執行時對程式碼採用逐行解釋執行,程式的執行效能可想而知。但是如今的HotSpot VM中不僅內建有直譯器,還內建有先進的JIT(Just In Time Compiler)編譯器,在Java虛擬機器執行時,直譯器和即時編譯器能夠相互協作,各自取長補短。在此大家需要注意,無論是採用直譯器進行解釋執行,還是採用即時編譯器進行編譯執行,最終位元組碼都需要被轉換為對應平臺的本地機器指令。或許有些開發人員會感覺到詫異,既然HotSpot VM中已經內建JIT編譯器了,那麼為什麼還需要再使用直譯器來“拖累”程式的執行效能呢?比如JRockit VM內部就不包含直譯器,位元組碼全部都依靠即時編譯器編譯後執行,儘管程式的執行效能會非常高效,但程式在啟動時必然需要花費更長的時間來進行編譯。對於服務端應用來說,啟動時間並非是關注重點,但對於那些看中啟動時間的應用場景而言,或許就需要採用直譯器與即時編譯器並存的架構來換取一個平衡點。
既然HotSpot VM中採用了即時編譯器,那麼這就意味著將位元組碼編譯為本地機器指令是一件執行時任務。在HotSpot VM中內嵌有兩個JIT編譯器,分別為Client Compiler和Server Compiler,但大多數情況下我們簡稱為C1編譯器和C2編譯器。開發人員可以通過如下命令顯式指定Java虛擬機器在執行時到底使用哪一種即時編譯器,如下所示:
-client:指定Java虛擬機器執行在Client模式下,並使用C1編譯器;
-server:指定Java虛擬機器執行在Server模式下,並使用C2編譯器。
除了可以顯式指定Java虛擬機器在執行時到底使用哪一種即時編譯器外,預設情況下HotSpot VM則會根據作業系統版本與物理機器的硬體效能自動選擇執行在哪一種模式下,以及採用哪一種即時編譯器。簡單來說,C1編譯器會對位元組碼進行簡單和可靠的優化,以達到更快的編譯速度;而C2編譯器會啟動一些編譯耗時更長的優化,以獲取更好的編譯質量。不過在Java7版本之後,一旦開發人員在程式中顯式指定命令“-server”時,預設將會開啟分層編譯(Tiered Compilation)策略,由C1編譯器和C2編譯器相互協作共同來執行編譯任務。不過在早期版本中,開發人員則只能夠通過命令“-XX:+TieredCompilation”手動開啟分層編譯策略。
之前筆者曾經提及過,預設情況下HotSpot VM是採用直譯器與即時編譯器並存的架構,當然開發人員可以根據具體的應用場景,通過命令顯式地為Java虛擬機器指定在執行時到底是完全採用直譯器執行,還是完全採用即時編譯器執行。如下所示:
-Xint:完全採用直譯器模式執行程式;
-Xcomp:完全採用即時編譯器模式執行程式;
-Xmixed:採用直譯器+即時編譯器的混合模式共同執行程式。
在此大家需要注意,如果Java虛擬機器在執行時完全採用直譯器執行,那麼即時編譯器將會停止所有的工作,位元組碼將完全依靠直譯器逐行解釋執行。反之如果Java虛擬機器在執行時完全採用即時編譯器執行,但直譯器仍然會在即時編譯器無法進行的特殊情況下介入執行,以確保程式能夠最終順利執行。
由於即時編譯器將本地機器指令的編譯推遲到了執行時,自此Java程式的執行效能已經達到了可以和C/C++程式一較高下的地步。這主要是因為JIT編譯器可以針對那些頻繁被呼叫的“熱點程式碼”做出深度優化,而靜態編譯器的程式碼優化則無法完全推斷出執行時熱點,因此通過JIT編譯器編譯的本地機器指令比直接生成的本地機器指令擁有更高的執行效率也就理所當然了。比如使用Python實現的PyPy執行器,比使用C實現的CPython直譯器更加靈活,更重要的是,在程式的執行效能上進行比較,PyPy將近是CPython直譯器執行效率的1至5倍,這就是對JIT技術魅力的一個有力證明。並且Java技術自身的諸多優勢同樣也是C/C++無法比擬的,所謂各有所長就是這個道理。在此大家需要注意,世界上永遠沒有最好的程式語言,只有最適用於具體應用場景的程式語言。