1. 程式人生 > >01 Java 代碼是怎麽運行的

01 Java 代碼是怎麽運行的

虛擬機 lse 正式 地方法 圖片 公眾 類型 正常 地方

Java代碼運行的方式

1:在開發工具中運行
2:雙擊 jar 文件運行
3:在命令行中運行
4:在網頁中運行

上述運行方式都離不開 JRE,也就是 Java 運行時環境。實際上 JRE 僅包含運行 Java 程序的必須組件,包括 Java 虛擬機以及 Java 核心類庫等。Java 程序員經常接觸到的 JDK 同樣包含了 JRE,並且還附帶了一系列開發和診斷工具。

為什麽 Java 要在虛擬機裏運行

Java 是一門高級程序語言,語法復雜,抽象程度高,因此直接在硬件上運行並不現實。所以,在 Java 程序運行之前,需要對其進行轉換。

設計一個面向 Java 語言的特性的虛擬機,並通過編譯器將 Java 程序轉換成該虛擬機所能識別的指令程序,也叫 Java 字節碼。之所以叫做字節碼,是因為 Java 字節碼指令的操作碼被固定為一個字節。

使用虛擬機的好處

Java 虛擬機也可以由硬件直接實現,但是更為常見的是基於軟件實現。

如果一個 Java 程序被轉換成 Java 字節碼,那麽他便可以在不同平臺上的 Java 虛擬機實現裏運行。這就是通常所說的:一次編譯,到處運行。

虛擬機的另一個好處就是它提供了一個托管環境。這個托管環境代替我們處理一些代碼中冗長而且容易出錯的代碼。最明顯的使用就是自動內存管理和垃圾回收。托管環境還提供了諸如數組越界,動態類型,安全權限等等動態監測。

Java 虛擬機如何運行 Java 字節碼

下面以標準 JDK 中的 HotSpot 虛擬機為例,從虛擬機以及底層硬件兩個角度進行分析。

從虛擬機的視角來看,首先將 Java 代碼編譯成 class 文件加載到 Java 虛擬機中。加載後的 Java 類會被存放於方法區,實際運行時,虛擬機會執行方法區的代碼。

Java 虛擬機在內存中劃分出堆和棧來存儲運行時的數據。對於棧, Java 虛擬機會把其細分為面向 Java 方法的 Java 方法棧,面向本地方法(用 C++ 寫的 native 方法)的本地方法棧,以及存放各個線程執行位置的 PC 寄存器。

技術分享圖片

在虛擬機運行過程中,每當調用進入一個 Java 方法, Java 虛擬機會在當前線程的 Java 方法棧中生成一個棧幀,用以存放局部變量以及字節碼的操作數。

當退出當前執行的方法時,不管是正常返回還是異常返回,Java 虛擬機均會彈出當前線程的當前棧幀,將之舍棄。

從硬件的視角來看,Java 字節碼無法直接執行,因此 Java 虛擬機需要將字節碼翻譯成機器碼。

翻譯的形式有兩種,一種是解釋執行,即逐條將字節碼翻譯成機器碼執行;一種是即時編譯,即將一個方法中包含的所有字節碼編譯成機器碼後再執行。

技術分享圖片

解釋執行的優勢在於無需等待編譯,及時編譯的優勢在於實際運行速度會更快。HotSpot 默認采用混合模式,綜合了解釋執行和即時編譯兩者的優點。它會先解釋執行字節碼,而後將其中反復執行的熱點代碼,以方法為單位進行即時編譯。

Java 虛擬機的運行效率

HotSpot 采用了多種技術來提升啟動性能以及峰值性能,即時編譯就是其中最重要的技術之一。

為了滿足不同用戶場景的需要,HotSpot 內置了多個即時編譯器:C1,C2 和 Graal。Graal 是 Java 10 正式引入的實驗性即時編譯器,後續再總結。引入多個即時編譯器,是為了在編譯時間和生成代碼的執行效率之間進行取舍。

C1 又叫做 Client 編譯器,面向的是對啟動性能有要求的客戶端 GUI 程序,采用的優化手段相對簡單,因此編譯時間較短。

C2 又叫做 Server 編譯器,面向的是對峰值性能有要求的服務器端程序,采用的優化手段相對復雜,因此編譯時間較長,但生成代碼的執行效率較高。

從 Java 7 開始,HotSpot 默認次用分層編譯的方式:熱點方法首先會被 C1 編譯,而後熱點方法中的熱點會進一步被 C2 編譯。

為了不幹擾應用正常的運行,HotSpot 的即時編譯是放在額外的編譯線程中進行的。HotSpot 會根據 CPU 的數量設置編譯線程的數目,並且按 1:2 的比例分配給 C1 和 C2 編譯器。

問答

Q:對於發布一次就長時間運行的程序,為什麽不選擇直接將 Java 字節碼編譯成機器碼

事實上 JVM 卻是有考慮做 AOT 的這種事情。AOT 能夠在線下將 Java 字節碼編譯成機器碼,主要用來解決啟動性能不好的問題。其實線下編譯和即時編譯都一樣,至多一兩個小時後該編譯的都已經編譯完成了。另外,即時編譯器因為有程序運行時信息,優化效果更好,也就是說峰值性能更好。

Q:如何區分熱點代碼和非熱點代碼

關於熱點代碼的統計有兩種算法:一種是基於采樣的熱點探測,一種是基於計數器的熱點探測。基於計數器的熱點探測又有兩個計數器:一種是方法調用計數器,一種是回邊計數器,他們在 C1 和 C2 中有不同的閾值。默認的分層編譯應該是達到兩千調用 C1,達到一萬五調用 C2。一般采用的都是基於計數器的熱點探測。

Q:對於 JVM 的及時編譯,當方法體中有很多 if,else if 這樣的判斷,如何編譯

JVM 有兩種編譯方式,一種是對整個方法進行編譯,一種是對熱循環進行編譯。無論哪種,都要比 if else 的粒度大。

總結

本文創作靈感來源於 極客時間 鄭雨迪老師的《深入拆解 Java 虛擬機》課程,通過課後反思以及借鑒各位學友的發言總結,現整理出自己的知識架構,以便日後溫故知新,查漏補缺。

關註本人公眾號,第一時間獲取最新文章發布,每日更新一篇技術文章。

技術分享圖片

01 Java 代碼是怎麽運行的