最近因為公司的一些原因,我也開始學習一些 JAVA 的知識。雖然我一直是以 .NET 語言為主的程式設計師,但是我並不排斥任何其它語言。在此並不討論 JAVA .NET 的好壞,僅僅是對 .NET 跟 JAVA 程式的編譯執行過程進行一些簡單的介紹跟比較。因為有些內容還是超出自己原來的認知的,所以整理一下做個記錄。
.NET
.NET 程式的執行過程大概分以下幾個步驟:
- 程式碼
- 語言編譯器編譯
- IL
- JIT 編譯
- 執行
.NET 平臺的程式編譯的時候是分多步的。當我們寫好程式碼開始編譯的時候需要選擇一個合適的編譯器比如csc 、vbc 。經過這一次編譯之後我們的程式會被打包成 dll
或者 .exe 檔案。這些 dll 裡面其實包含的是 MSIL 。IL 做為一種中間語言,為跨平臺提供了基礎。當我們把這些檔案複製到目標機器上需要真正執行的時候,JIT (just-in-time compilation)編譯開始工作了。CLR 為我們在每個支援的平臺上都實現了一個 JIT 編譯器,當一個方法在第一次執行的時候,JIT 編譯會把 IL 編譯成目標機器的機器碼,這樣我們的程式才能真正執行。這也是為什麼 .NET 程式第一次執行的時候會慢一點的原因。解決這個問題我們可以使用工具 Ngen.exe 在第一次執行前進行一次預編譯,這樣就可以提升 .NET 程式的啟動速度。
分層編譯
上面大概描述了 .NET 程式編譯過程。但是 JIT 編譯可能還有一些特性需要講一下,比如分層編譯。
分層編譯是從 .NET core 2.1 開始引入的一個特性。我們的 IL 到機器碼,需要 JIT 進行一次編譯,這會影響 .NET 程式的第一次執行的速度。微軟為了解決這個問題引入了分層編譯。分層編譯把 JIT 編譯分成兩次。當一個方法第一次被執行的時候,JIT 編譯器會進行第一次快速編譯,這次編譯並不會進行特別的優化操作,追求的是編譯的速度。當我們的程式執行一段時間後,CLR 會自動感知到頻繁執行的程式碼,這些程式碼被稱為熱點程式碼。當出現熱點程式碼的時候 JIT 編譯器會重新進行一次優化編譯來提高熱點程式碼的執行效率,從而提高整個程式的效能。
通過 JIT 分層編譯, .NET 程式很好的在編譯速度跟效能之間找到了平衡。
JAVA
JAVA 程式的執行過程大概分以下幾個步驟:
- 程式碼
- 語言編譯器編譯
- 位元組碼
- 解釋/JIT編譯
- 執行
下面說說 JAVA 程式的編譯過程。
當我們編寫好 JAVA 程式,想要執行的時候,跟 .NET 程式一樣,同樣會選擇一個語言編譯器來進行第一次編譯。因為 JVM 語言有好多種,比如 JAVA ,kotlin ,所以同樣會有多種語言編譯器,比如 javac,kotlinc 等等。這裡還是以標準的 JAVA 為例,在語言編譯器編譯完原始碼後,會生成一堆 .class 的檔案,這些檔案包含的內容被稱之為位元組碼。位元組碼的存在跟 MSIL 類似,同樣為跨平臺提供了一種很好的方案。只要為每個平臺實現介面一致的 JVM , 讓這些 JVM 來執行位元組碼就可以跨平臺了。
解釋執行
當我們真正要執行 JAVA 程式的時候,這些位元組碼會被 JVM 執行。JVM 執行的時候首先會在 CodeCache 內查詢這個方法有沒有編譯好的機器程式碼,如果沒有那麼交給“解釋執行器”來解釋執行。所謂解釋執行,就是將程式碼一行行的經過直譯器進行翻譯成機器碼後讓目標機器執行。但是這些翻譯的產物並不會被記錄下來,也就是說同樣的程式碼每次執行的時候都需要直譯器進行翻譯。
JIT 編譯
顯然對於一些重複執行的方法直譯器執行效率會很低。為了解決這個問題,設計 JVM 的工程師們想出了辦法。以 Hotspot 為例,當程式經過一段時間的解釋執行後,JVM 會記錄這些方法的執行次數,當一些方法反覆被執行的時候,JVM 會認為這些方法是熱點程式碼。這時候 JVM 會對這些熱點程式碼進行一次 JIT 編譯,這次 JIT 編譯還會根據執行時的 profile 進行優化。編譯完成後把 JIT 編譯的產物固定下來,儲存在 CodeCache 中。這樣當一個方法下次再次被執行的時候 JVM 會從 CodeCache 中直接讀取機器碼來執行。這樣熱點程式碼的執行效率就會大大的提供,這也是為啥有些 JAVA 程式需要進行預熱。
總結
通過以上我們分別描述了 .NET 跟 JAVA 程式編譯執行的過程。他們之間的區別在於 .NET 程式不管什麼時候都是進行 JIT 編譯,並且通過分層編譯技術在首次執行速度跟效能之間找到了平衡。而 JAVA 雖然做為一門靜態語言,但是它的程式碼一開始竟然是解釋執行的(當然這是對 Hotspot JVM而言的,有的 JVM 未必是這樣),在執行的時候才會對熱點程式碼進行 JIT 編譯優化程式碼。雖然大家實現的方式不同,但是殊途同歸,都是通過對熱點程式碼的二次編譯實現了對程式的效能的優化。
參考
https://docs.microsoft.com/zh-cn/dotnet/standard/managed-execution-process
https://www.zhihu.com/question/37389356/answer/73820511