你編寫的Java代碼是咋跑起來的?

若是你是一名 Java 開發人員,你確定指定 Java 代碼有不少種不一樣的運行方式。好比說能夠在開發工具(IDEA、Eclipse等)中運行,能夠雙擊執行 jar 文件運行,也能夠在命令行中運行,甚至能夠在網頁(好比各類 OJ)中運行。固然,這些執行方式都離不開 JRE(Java 運行時環境)。java

JRE 包含運行 Java 程序的必需組件,包括 JVM(Java 虛擬機)以及 Java 核心類庫等。Java 程序員常常接觸到的 JDK(Java 開發工具包)一樣包含了 JRE,而且還附帶了一系列開發、診斷工具。程序員

本篇文章主要針對如下兩個問題和你們一塊兒探討:算法

  1. 爲何須要 JVM?
  2. JVM 是怎樣運行 Java 代碼的呢?

爲何須要 JVM?

Java 的一個很是重要的特色就是與平臺的無關性,而使用 JVM 是實現這一特色的關鍵。Java 做爲一門高級程序語言,語法複雜,抽象程度高。所以,直接在硬件上運行這種複雜的程序並不現實。因此在運行 Java 程序以前,咱們須要對其進行轉換。數組

設計一個面向 Java 語言特性的虛擬機,並經過編譯器將 Java 程序轉換成該虛擬機所能識別的指令序列(由於 Java 字節碼指令的操做碼(opcode)被固定爲一個字節,故又稱 Java 字節碼)。安全

JVM 通常是在各個現有平臺(如 Windows、Linux)上提供軟件實現,這樣可使一旦一個程序被轉換成 Java 字節碼,那麼即可以在不一樣平臺上的虛擬機實現裏運行(一次編寫,處處運行)。服務器

JVM 另一個好處是帶有託管環境(Managed Runtime),託管環境可以代替處理一些代碼中冗長並且容易出錯的部分,其中包括自動內存管理與垃圾回收(GC)。jvm

另外,託管環境還提供了諸如數組越界、動態類型、安全權限等等的動態檢測,使咱們免於書寫這些無關業務邏輯的代碼。工具

JVM 是怎樣運行 Java 代碼的呢?

JVM 具體是怎麼運行 Java 字節碼的呢?下面咱們一塊兒來看一下:性能

從 JVM 來看,執行 Java 代碼首先須要將它編譯而成的 class 文件加載到 JVM 中。加載後的 Java 類會被存放於方法區(Method Area)中。實際運行時,JVM 會執行方法區內的代碼。開發工具

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

在運行過程當中,每當調用進入一個 Java 方法,JVM 會在當前線程的 Java 方法棧中生成一個棧幀,用以存放局部變量以及字節碼的操做數。棧幀的大小是提早計算好的,並且 JVM 不要求棧幀在內存空間裏連續分佈。

當退出當前執行的方法時,不論是正常返回仍是異常返回,JVM 均會彈出當前線程的當前棧幀,並將之捨棄。

從硬件視角來看,Java 字節碼沒法直接執行。所以,JVM 須要將字節碼翻譯成機器碼。

在 HotSpot 裏面,上述翻譯過程有兩種形式:第一種是解釋執行(interpreter),即逐條將字節碼翻譯成機器碼並執行;第二種是即時編譯(Just-In-Time compilation,JIT),即將一個方法中包含的全部字節碼編譯成機器碼後再執行。

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

整個 Java 代碼執行過程以下:

  1. 使用 javac 把 .java 源文件編譯爲字節碼(文件後綴名爲 .class)
  2. 字節碼通過 JIT 環境變量進行判斷,是否屬於熱點代碼(屢次調用的方法或循環體)
  3. 熱點代碼使用 JIT 編譯爲可執行的機器碼
  4. 非熱點代碼使用解釋器解釋執行全部字節碼

其中,在運行過程當中會被即時編譯的熱點代碼有兩類:

  1. 被屢次調用的方法
  2. 被屢次執行的循環體

針對第一類,編譯器會將整個方法做爲編譯對象,這也是標準的 JIT 編譯方式。對於第二類是由循環體出發的,可是編譯器依然會以整個方法做爲編譯對象,由於發生在方法執行過程當中,稱爲棧上替換。

HotSpot 採用了多種技術來提高啓動性能以及峯值性能,剛剛提到的即時編譯即是其中最重要的技術之一。

即時編譯創建在程序符合二八定律的假設上,也就是百分之二十的代碼佔據了百分之八十的計算資源。

對於佔據大部分的不經常使用的代碼,咱們無需耗費時間將其編譯成機器碼,而是採起解釋執行的方式運行;另外一方面,對於僅佔據小部分的熱點代碼,咱們則能夠將其編譯成機器碼,以達到理想的運行速度。

爲了知足不一樣用戶場景的須要,HotSpot 內置了多個即時編譯器:C一、C2。之因此引入多個即時編譯器,是爲了在編譯時間和生成代碼的執行效率之間進行取捨。

  • C1 (Client 編譯器)面向的是對啓動性能有要求的客戶端 GUI 程序,採用的優化手段相對簡單,所以編譯時間較短。
  • C2 (Server 編譯器)面向的是對峯值性能有要求的服務器端程序,採用的優化手段相對複雜,所以編譯時間較長,但同時生成代碼的執行效率較高。

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

爲了避免干擾應用的正常運行,HotSpot 的即時編譯是放在額外的編譯線程中進行的。HotSpot 會根據 CPU 的數量設置編譯線程的數目,而且按 1:2 的比例配置給 C1 及 C2 編譯器。

在計算資源充足的狀況下,字節碼的解釋執行和即時編譯可同時進行。編譯完成後的機器碼會在下次調用該方法時啓用,以替換本來的解釋執行。

其中判斷一段代碼是否爲熱點代碼,是否是須要觸發即時編譯,這樣的行爲稱爲熱點探測(Hot Spot Detection),探測算法有兩種:

  1. 基於採樣的熱點探測(Sample Based Hot Spot Detection):虛擬機會週期的對各個線程棧頂進行檢查,若是某些方法常常出如今棧頂,這個方法就是熱點方法。優勢是實現簡單、高效,很容易獲取方法調用關係。缺點是很難確認方法的 reduce,容易受到線程阻塞或其餘外因擾亂。
  2. 基於計數器的熱點探測(Counter Based Hot Spot Detection):爲每一個方法(甚至是代碼塊)創建計數器,執行次數超過閾值就認爲是熱點方法。優勢是統計結果精確嚴謹。缺點是實現麻煩,不能直接獲取方法的調用關係。

HotSpot 使用的是第二種-基於計數器的熱點探測,而且有兩類計數器:方法調用計數器(Invocation Counter)和回邊計數器(Back Edge Counter)。

總結

這篇文章主要介紹了爲何須要 JVM 以及 JVM 是怎樣運行 Java 代碼的。

爲何須要 JVM:

  1. 提供了可移植性。一次編譯,處處執行。
  2. 提供了代碼託管的環境,代替處理部分冗長並且容易出錯的部分。

JVM 將運行時內存區域劃分爲五個部分,分別爲方法區、堆、PC 寄存器、Java 方法棧和本地方法棧。Java 程序編譯而成的 class 文件,須要先加載至方法區中,方能在 JVM 中運行。

爲了提升運行效率,HotSpot 虛擬機採用的是一種混合執行的策略,會解釋執行 Java 字節碼,而後會將其中反覆執行的熱點代碼,以方法爲單位進行即時編譯,翻譯成機器碼後直接運行在底層硬件之上。

HotSpot 裝載了多個不一樣的即時編譯器,以便在編譯時間和生成代碼的執行效率之間作取捨。

判斷熱點代碼的探測算法包括基於採樣和基於計數器兩種,HotSpot 採用基於計數器的熱點探測,計數器又分爲方法調用計數器和回邊計數器。

相關文章
相關標籤/搜索