這篇文的動機是因為最近一直聽到別人勸告新手與其學 java 不如學 c ,原因是java速度很慢,這個理由我不太能接受,所以就寫一篇文章來說明為什麼 java 的速度其實跟 c、c++ 差不多,在特定情況下藉由 JIT 技術已經比 C 或 C++ 快了。

首先,我們要知道 java 的編譯過程。

java 和 c 的 compiler 滿不一樣的,java 不同於 c 直接 compile 成 CPU 的指令,他是產生**統一規格的 bytecode,在運行期間,JVM 才會 interpret bytecode,所以只需要 build 一份 bytecode 就可以在各平台上運行。

這樣的編譯流程帶來跨平台的好處之外,還有運行效能的缺點,所以在2000年左右,java開始研究 HotSpot 的核心技術。

這裡要先講一個滿常發生的程式特性,大部分的運行時間被運用在少數的程式碼上

JIT的在編譯階段的優化

hot spot 的意思是 bytecode 當中常被使用到的程式碼片段,JVM 在運行程式的時候,先分析和找出這些程式碼片段,接下來將這些程式碼片段做大範圍的最佳化、以及將程式碼 compile 成特定處理器的指令。

簡單來說,就是編譯器自動幫你重構被重複使用的程式碼來加速程式的效能。(題外話,Oracle 推薦大家用他的 JDK 的理由就是他自稱有很多的黑科技優化,但很遺憾我還是選擇 openJDK。)

這是一張 C 和 java 的效能比較圖,比較項目是大矩陣的運算耗時,詳情請看文章

C 語言在使用 GCC 編譯時,若未經過 -O2 and -O3 optimization,速度其實不會比 java 快,當然這是因為GCC把這些黑科技優化鎖起來了的關係,java 的優勢我覺得還是在開發上面的便利和生態系,所謂的 java 比 c 或 c++ 快的論點我覺得都是在特定情況下的吹捧。

但是還是希望大家能夠有一個概念,現代的 java 速度跟 c 的差異其實沒有大家想像的大

程式語言效能圖,強者我朋友mes在golang教學中載的圖忘記來源。

(由於程式碼寫法的問題,不一定所有的 source code 被編譯的時候都會被優化,有的更可能造成執行速度變慢)

JIT的在執行階段的優化

如果有一段組合語言在程式碼當中出現了1000次,直譯器(interpreter) 會重複組譯1000次同樣的機器語言,JIT 會將這段機器語言先保留在快取記憶體內,每當遇到的時後就直接拿這段機器語言來執行,不用再組譯999次。

JIT的編譯情形

此外,為了降低 JIT 對編譯造成的負擔,JIT目前僅針對迴圈等會重複出現的程式碼做優化的動作。

JIT的技術支持

JIT 很大一部分建立於 java 的 garbage collection 之上,若想要更進一步的了解可以參閱官方說明

安全問題

JIT 編譯的實現包括將 source code 或 byte code 編譯為機器代碼並執行。通常這是直接在內存中完成的 -JIT 編譯器將機器代碼直接輸出到內存中並立即執行,而不是將其輸出到硬碟上。

然後像大部分的預編譯一樣,將程式碼作為單獨的程序調用。在現代體系結構中,由於 Executable space protection (可執行空間)的保護,這會帶來一個問題–無法執行任意記憶體,否則會存在潛在的安全漏洞。

因此,必須將記憶體先標記為可執行。出於安全原因,應在將代碼寫入內存並標記為只讀之後執行此操作。

JIT Spraying 是一類計算機安全漏洞利用,它使用 JIT compilation for heap spraying,然後生成的記憶體是可執行的,如果可以將執行轉移到 heap 當中,就可以利用該漏洞。

afan閒聊

java 16 優化了他的 garbage collection 功能,利用平行處理把垃圾回收機制的運行壓在 1ms (毫秒)之內,又更快了。

話說在去年底,PHP 8.0 也引入了 JIT,雖然PHP是世界上最好的語言梗一直被玩,但好像真的有在不斷改進與進化。