关于 .NET 与 JAVA 在 JIT 编译上的一些差异
最近因?yàn)楣镜囊恍┰?#xff0c;我也開始學(xué)習(xí)一些 JAVA 的知識。雖然我一直是以 .NET 語言為主的程序員,但是我并不排斥任何其它語言。在此并不討論 JAVA .NET 的好壞,僅僅是對 .NET 跟 JAVA 程序的編譯執(zhí)行過程進(jìn)行一些簡單的介紹跟比較。因?yàn)橛行﹥?nèi)容還是超出自己原來的認(rèn)知的,所以整理一下做個記錄。
.NET
.NET 程序的執(zhí)行過程大概分以下幾個步驟:
1.代碼2.語言編譯器編譯3.IL4.JIT 編譯5.運(yùn)行
.NET 平臺的程序編譯的時候是分多步的。當(dāng)我們寫好代碼開始編譯的時候需要選擇一個合適的編譯器比如csc 、vbc 。經(jīng)過這一次編譯之后我們的程序會被打包成 dll 或者 .exe 文件。這些 dll 里面其實(shí)包含的是 MSIL 。IL 做為一種中間語言,為跨平臺提供了基礎(chǔ)。當(dāng)我們把這些文件復(fù)制到目標(biāo)機(jī)器上需要真正運(yùn)行的時候,JIT (just-in-time compilation)編譯開始工作了。CLR 為我們在每個支持的平臺上都實(shí)現(xiàn)了一個 JIT 編譯器,當(dāng)一個方法在第一次運(yùn)行的時候,JIT 編譯會把 IL 編譯成目標(biāo)機(jī)器的機(jī)器碼,這樣我們的程序才能真正運(yùn)行。這也是為什么 .NET 程序第一次運(yùn)行的時候會慢一點(diǎn)的原因。解決這個問題我們可以使用工具 Ngen.exe 在第一次運(yùn)行前進(jìn)行一次預(yù)編譯,這樣就可以提升 .NET 程序的啟動速度。
分層編譯
上面大概描述了 .NET 程序編譯過程。但是 JIT 編譯可能還有一些特性需要講一下,比如分層編譯。分層編譯是從 .NET core 2.1 開始引入的一個特性。我們的 IL 到機(jī)器碼,需要 JIT 進(jìn)行一次編譯,這會影響 .NET 程序的第一次運(yùn)行的速度。微軟為了解決這個問題引入了分層編譯。分層編譯把 JIT 編譯分成兩次。當(dāng)一個方法第一次被執(zhí)行的時候,JIT 編譯器會進(jìn)行第一次快速編譯,這次編譯并不會進(jìn)行特別的優(yōu)化操作,追求的是編譯的速度。當(dāng)我們的程序運(yùn)行一段時間后,CLR 會自動感知到頻繁運(yùn)行的代碼,這些代碼被稱為熱點(diǎn)代碼。當(dāng)出現(xiàn)熱點(diǎn)代碼的時候 JIT 編譯器會重新進(jìn)行一次優(yōu)化編譯來提高熱點(diǎn)代碼的執(zhí)行效率,從而提高整個程序的性能。
通過 JIT 分層編譯, .NET 程序很好的在編譯速度跟性能之間找到了平衡。
JAVA
JAVA 程序的執(zhí)行過程大概分以下幾個步驟:
1.代碼2.語言編譯器編譯3.字節(jié)碼4.解釋/JIT編譯5.運(yùn)行
下面說說 JAVA 程序的編譯過程。
當(dāng)我們編寫好 JAVA 程序,想要執(zhí)行的時候,跟 .NET 程序一樣,同樣會選擇一個語言編譯器來進(jìn)行第一次編譯。因?yàn)?JVM 語言有好多種,比如 JAVA ,kotlin ,所以同樣會有多種語言編譯器,比如 javac,kotlinc 等等。這里還是以標(biāo)準(zhǔn)的 JAVA 為例,在語言編譯器編譯完源代碼后,會生成一堆 .class 的文件,這些文件包含的內(nèi)容被稱之為字節(jié)碼。字節(jié)碼的存在跟 MSIL 類似,同樣為跨平臺提供了一種很好的方案。只要為每個平臺實(shí)現(xiàn)接口一致的 JVM , 讓這些 JVM 來運(yùn)行字節(jié)碼就可以跨平臺了。
解釋執(zhí)行
當(dāng)我們真正要執(zhí)行 JAVA 程序的時候,這些字節(jié)碼會被 JVM 執(zhí)行。JVM 執(zhí)行的時候首先會在 CodeCache 內(nèi)查找這個方法有沒有編譯好的機(jī)器代碼,如果沒有那么交給“解釋執(zhí)行器”來解釋執(zhí)行。所謂解釋執(zhí)行,就是將代碼一行行的經(jīng)過解釋器進(jìn)行翻譯成機(jī)器碼后讓目標(biāo)機(jī)器執(zhí)行。但是這些翻譯的產(chǎn)物并不會被記錄下來,也就是說同樣的代碼每次執(zhí)行的時候都需要解釋器進(jìn)行翻譯。
JIT 編譯
顯然對于一些重復(fù)執(zhí)行的方法解釋器執(zhí)行效率會很低。為了解決這個問題,設(shè)計(jì) JVM 的工程師們想出了辦法。以 Hotspot 為例,當(dāng)程序經(jīng)過一段時間的解釋執(zhí)行后,JVM 會記錄這些方法的執(zhí)行次數(shù),當(dāng)一些方法反復(fù)被執(zhí)行的時候,JVM 會認(rèn)為這些方法是熱點(diǎn)代碼。這時候 JVM 會對這些熱點(diǎn)代碼進(jìn)行一次 JIT 編譯,這次 JIT 編譯還會根據(jù)運(yùn)行時的 profile 進(jìn)行優(yōu)化。編譯完成后把 JIT 編譯的產(chǎn)物固定下來,存儲在 CodeCache 中。這樣當(dāng)一個方法下次再次被執(zhí)行的時候 JVM 會從 CodeCache 中直接讀取機(jī)器碼來執(zhí)行。這樣熱點(diǎn)代碼的執(zhí)行效率就會大大的提供,這也是為啥有些 JAVA 程序需要進(jìn)行預(yù)熱。
總結(jié)
通過以上我們分別描述了 .NET 跟 JAVA 程序編譯執(zhí)行的過程。他們之間的區(qū)別在于 .NET 程序不管什么時候都是進(jìn)行 JIT 編譯,并且通過分層編譯技術(shù)在首次執(zhí)行速度跟性能之間找到了平衡。而 JAVA 雖然做為一門靜態(tài)語言,但是它的代碼一開始竟然是解釋執(zhí)行的(當(dāng)然這是對 Hotspot JVM而言的,有的 JVM 未必是這樣),在運(yùn)行的時候才會對熱點(diǎn)代碼進(jìn)行 JIT 編譯優(yōu)化代碼。雖然大家實(shí)現(xiàn)的方式不同,但是殊途同歸,都是通過對熱點(diǎn)代碼的二次編譯實(shí)現(xiàn)了對程序的性能的優(yōu)化。
參考
https://docs.microsoft.com/zh-cn/dotnet/standard/managed-execution-process
https://www.zhihu.com/question/37389356/answer/73820511
關(guān)注我的公眾號一起玩轉(zhuǎn)技術(shù)
總結(jié)
以上是生活随笔為你收集整理的关于 .NET 与 JAVA 在 JIT 编译上的一些差异的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hello Blazor:(8)启用深色
- 下一篇: 不会自动化UI测试?不会编程?没问题,会