關於 .NET 與 JAVA 在 JIT 編譯上的一些差異


最近因為公司的一些原因,我也開始學習一些 JAVA 的知識。雖然我一直是以 .NET 語言為主的程序員,但是我並不排斥任何其它語言。在此並不討論 JAVA .NET 的好壞,僅僅是對 .NET 跟 JAVA 程序的編譯執行過程進行一些簡單的介紹跟比較。因為有些內容還是超出自己原來的認知的,所以整理一下做個記錄。

.NET

.NET 程序的執行過程大概分以下幾個步驟:

  1. 代碼
  2. 語言編譯器編譯
  3. IL
  4. JIT 編譯
  5. 運行

.NET 平台的程序編譯的時候是分多步的。當我們寫好代碼開始編譯的時候需要選擇一個合適的編譯器比如csc 、vbc 。經過這一次編譯之后我們的程序會被打包成 dll
或者 .exe 文件。這些 dll 里面其實包含的是 MSIL 。IL 做為一種中間語言,為跨平台提供了基礎。當我們把這些文件復制到目標機器上需要真正運行的時候,JIT (just-in-time compilation)編譯開始工作了。CLR 為我們在每個支持的平台上都實現了一個 JIT 編譯器,當一個方法在第一次運行的時候,JIT 編譯會把 IL 編譯成目標機器的機器碼,這樣我們的程序才能真正運行。這也是為什么 .NET 程序第一次運行的時候會慢一點的原因。解決這個問題我們可以使用工具 Ngen.exe/Crossgen 在第一次運行前進行一次預編譯,這樣就可以提升 .NET 程序的啟動速度。

分層編譯

上面大概描述了 .NET 程序編譯過程。但是 JIT 編譯可能還有一些特性需要講一下,比如分層編譯。
分層編譯是從 .NET core 2.1 開始引入的一個特性。我們的 IL 到機器碼,需要 JIT 進行一次編譯,這會影響 .NET 程序的第一次運行的速度。微軟為了解決這個問題引入了分層編譯。分層編譯把 JIT 編譯分成兩次。當一個方法第一次被執行的時候,JIT 編譯器會進行第一次快速編譯,這次編譯並不會進行特別的優化操作,追求的是編譯的速度。當我們的程序運行一段時間后,CLR 會自動感知到頻繁運行的代碼,這些代碼被稱為熱點代碼。當出現熱點代碼的時候 JIT 編譯器會重新進行一次優化編譯來提高熱點代碼的執行效率,從而提高整個程序的性能。
通過 JIT 分層編譯, .NET 程序很好的在編譯速度跟性能之間找到了平衡。

JAVA

JAVA 程序的執行過程大概分以下幾個步驟:

  1. 代碼
  2. 語言編譯器編譯
  3. 字節碼
  4. 解釋/JIT編譯
  5. 運行

下面說說 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

關注我的公眾號一起玩轉技術


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM