JVM執行引擎理解


執行引擎概述

  • 執行引擎是Java虛擬機核心的組成部分之一。
  • “虛擬機”是一個相對於“物理機”的概念,這兩種機器都有代碼執行能力,其區別是物理機的執行引擎是直接建立在處理器、緩存、指令集和操作系統層面,上的,而虛擬機的執行引擎則是由軟件自行實現的,因此可以不受物理條件制約地定制指令集與執行引擎的結構體系,能夠執行那些不被硬件直接支持的指令集格式。
  • JVM的主要任務是負責裝載字節碼到其內部,但字節碼並不能夠直接運行在操作系統之.上,因為字節碼指令並非等價於本地機器指令,它內部包含的僅僅只是一些能夠被JVM所識別的字節碼指令、符號表,以及其他輔助信息。
  • 那么,如果想要讓一個Java程序運行起來,執行引擎(Execution Engine)的任務就是將字節碼指令解釋/編譯為對應平台上的本地機器指令才可以。簡單來說,JVM中的執行引擎充當了將高級語言翻譯為機器語言的譯者。

執行引擎的工作過程

1。執行引擎在執行的過程中究竟需要執行什么樣的字節碼指令完全依賴於PC寄存器。
2。每當執行完一項指令操作后,PC寄存器就會更新下一條需要被執行的指令地址。
3。當然方法在執行的過程中,執行引擎有可能會通過存儲在局部變量表中的對象引用准確定位到存儲在Java堆區中的對象實例信息,以及通過對象頭中的元數據指針定位到目標對象的類型信息。

Java代碼編譯和執行過程

大部分的程序代碼轉換成物理機的目標代碼或虛擬機能執行的指令集之前,都需要經過上圖中的各個步驟。

Java代碼編譯是由Java源碼編譯器來完成,流程圖如下所示:

Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

什么是解釋器( Interpreter),什么是JIT編譯器?

  • 解釋器:當Java虛擬機啟動時會根據預定義的規范對字節碼采用逐行解釋的方式執行,將每條字節碼文件中的內容“翻譯”為對應平台的本地機器指令執行。

  • JIT (Just In Time Compiler)編譯器:就是虛擬機將源代碼直接編譯成和本地機器平台相關的機器語言

為什么說Java是半編譯半解釋型語言?

  • JDK1.0時代,將Java語言定位為“解釋執行”還是比較准確的。再后來,Java也發展出可以直接生成本地代碼的編譯器。
  • 現在JVM在執行Java代碼的時候,通常都會將解釋執行與編譯執行二者結合起來進行。

機器碼、指令、匯編語言

機器碼

  • 各種用二進制編碼方式表示的指令,叫做機器指令碼。開始,人們就用它采編寫程序,這就是機器語言。
  • 機器語言雖然能夠被計算機理解和接受,但和人們的語言差別太大,不易被人們理解和記憶,並且用它編程容易出差錯。
  • 用它編寫的程序一經輸入計算機,CPU直接讀取運行,因此和其他語言編的程序相比,執行速度最快。
  • 機器指令與CPU緊密相關,所以不同種類的CPU所對應的機器指令也就不同。

指令

  • 由於機器碼是有0和1組成的二進制序列,可讀性實在太差,於是人們發明了指令。
  • 指令就是把機器碼中特定的0和1序列,簡化成對應的指令(一般為英文簡寫,如mov, inc等),可讀性稍好
  • 由於不同的硬件平台,執行同一個操作,對應的機器碼可能不同,所以不同的硬件平台的同-種指令(比如mov),對應的機器碼也可能不同。

指令集

  • 不同的硬件平台,各自支持的指令,是有差別的。因此每個平台所支持的指令,稱之為對應平台的指令集。.
  • 如常見的
    ➢x86指令集,對應的是x86架構的平台
    ➢ARM指令集,對應的是ARM架構的平台

匯編語言

  • 由於指令的可讀性還是太差,於是人們又發明了匯編語言。
  • 在匯編語言中,用助記符(Mnemonics)代替機器指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或操作數的地址。
  • 在不同的硬件平台,匯編語言對應着不同的機器語言指令集,通過匯編過程轉換成機器指令。
    ➢由於計算機只認識指 令碼,所以用匯編語言編寫的程序還必須翻譯成機器指令碼,計算機才能識別和執行。

解釋器

JVM設計者們的初衷僅僅只是單純地為了滿足Java程序實現跨平台特性,因此避免采用靜態編譯的方式直接生成本地機器指令,從而誕生了實現解釋器在運行時采用逐行解釋字節碼執行程序的想法。

  • 解釋器真正意義上所承擔的角色就是一個運行時“翻譯者”,將字節碼文件中的內容“翻譯”為對應平台的本地機器指令執行。

  • 當一條字節碼指令被解釋執行完成后,接着再根據PC寄存器中記錄的下一條需要被執行的字節碼指令執行解釋操作。

在Java的發展歷史里,一共有兩套解釋執行器,即古老的字節碼解釋器、現在普遍使用的模板解釋器。

  • 字節碼解釋器在執行時通過純軟件代碼模擬字節碼的執行,效率非常低下。
  • 而模板解釋器將每一條字節碼和一個模板函數相關聯,模板函數中直接產生這條字節碼執行時的機器碼,從而很大程度上提高了解釋器的性能。
    ➢在HotSpot VM中,解釋器主要由Interpreter模塊和Code模塊構成。
    • Interpreter模塊:實現了解釋器的核心功能
    • Code模塊:用於管理HotSpot VM在運行時生成的本地機器指令

現狀

  • 由於解釋器在設計和實現上非常簡單,因此除了Java語言之外,還有許多高級語言同樣也是基於解釋器執行的,比如Python、Perl、Ruby等。但是在今天,基於解釋器執行已經淪落為低效的代名詞,並且時常被一些C/C++程序員所調侃。
  • 為了解決這個問題,JVM平台支持一種叫作即時編譯的技術。即時編譯的目的是避免函數被解釋執行,而是將整個函數體編譯成為機器碼,每次函數執行時,只執行編譯后的機器碼即可,這種方式可以使執行效率大幅度
    提升。
  • 不過無論如何,基於解釋器的執行模式仍然為中間語言的發展做出了不可磨滅的貢獻。

JIT編譯器

  • HotSpot VM是目前市面上高性能虛擬機的代表作之一。它采用解釋器與即時編譯器並存的架構。在Java虛擬機運行時,解釋器和即時編譯器能夠相互協作,各自取長補短,盡力去選擇最合適的方式來權衡編譯本地代碼的時間和直接解釋執行代碼的時間。
  • 在今天,Java程序的運行性能早已脫胎換骨,已經達到了可以和C/C++
    程序一較高下的地步。

HotSpot VM的執行方式

當虛擬機啟動的時候,解釋器可以首先發揮作用,而不必等待即時編譯器全部編譯完成再執行,這樣可以省去許多不必要的編譯時間。並且隨着程序運行時間的推移,即時編譯器逐漸發揮作用,根據熱點探測功能,將有價值的字節碼編譯為
本地機器指令,以換取更高的程序執行效率。

熱點代碼和探測方式

當然是否需要啟動JIT編譯器將字節碼直接編譯為對應平台的本地機器指令,則需要根據代碼被調用執行的頻率而定。關於那些需要被編譯為本地代碼的字節碼,也被稱之為“熱點代碼”,JIT編譯器在運行時會針對那些頻繁被調用的“熱點代碼”做出深度優化,將其直接編譯為對應平台的本地機器指令,以此提升Java程序的執行性能。

  • 一個被多次調用的方法,或者是一個方法體內部循環次數較多的循環體都可以被稱之為“ 熱點代碼”,因此都可以通過JIT編譯器編譯為本地機器指令。由於這種編譯方式發生在方法的執行過程中,因此也被稱之為棧上替換,或簡稱為OSR (On Stack Replacement)編譯。

  • 一個方法究竟要被調用多少次,或者一個循環體究竟需要執行多少次循環才可以達到這個標准?必然需要一個明確的閾值,JIT編譯器才會將這些“熱點代碼”編譯為本地機器指令執行。這里主要依靠熱點探測功能。

  • 目前HotSpot VM所采用的熱點探測方式是基於計數器的熱點探測。

  • 采用基於計數器的熱點探測,HotSpot VM將 會為每一個 方法都建立2個不同類型的計數器,分別為方法調用計數器(Invocation Counter) 和回邊計數器(Back Edge Counter) 。
    ➢方法調用計數器用於統計方法的調用次數
    ➢回邊計數器則用於統計循環體執行的循環次數

方法調用計數器

  • 這個計數器就用於統計方法被調用的次數,它的默認閾值在Client 模式下是 1500 次,在Server 模式下是10000次。超過這個閾值, 就會觸發JIT編譯。
  • 這個閾值可以通過虛擬機參數-XX:CompileThreshold來人為設定。
  • 當一個方法被調用時,會先檢查該方法是否存在被JIT編譯過的版本,如果存在,則優先使用編譯后的本地代碼來執行。如果不存在已被編譯過的版本,則將此方法的調用計數器值加1, 然后判斷方法調用計數器與回邊計數器值之和是否超過方法調用計數器的閾值。如果已超過閾值,那么將會向即時編譯器提交一個該方法的代碼編譯請求。
熱度衰減
  • 如果不做任何設置,方法調用計數器統計的並不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間之內方法被調用的次數。當超過一定的時間限度,如果方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那這個方法的調用計數器就會被減少一半,這個過程稱為方法調用計數器熱度的衰減(Counter Decay) ,而這段時間就稱為此方法統計的半衰周期(Counter Half Life Time)。
  • 進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數-XX:-UseCounterDecay來關閉熱度衰減,讓方法計數器統計方法調用的絕對次數,這樣,只要系統運行時間足夠長,絕大部分方法都會被編譯成本地代碼。
  • 另外,可以使用-XX:CounterHalfLifeTime 參數設置半衰周期的時間,單位是秒。

回邊計數器

它的作用是統計一個方法中循環體代碼執行的次數,在字節碼中遇到控制流向后跳轉的指令稱為“回邊” (Back Edge) 。顯然,建立回邊計數器統計的目的就是為了觸發OSR編譯。

HotSpot VM設置程序執行方式

缺省情況下HotSpot VM是采用解釋器與即時編譯器並存的架構,當然開發人員可以根據具體的應用場景,通過命令顯式地為Java虛擬機指定在運行時到底是完全采用解釋器執行,還是完全采用即時編譯器執行。如下所示:

  • -Xint:完全采用解釋器模式執行程序;
  • -Xcomp:完全采用即時編譯器模式執行程序。如果即時編譯出現問題,解釋器會介入執行。
  • -Xmixed: 采用解釋器+即時編譯器的混合模式共同執行程序。

在HotSpot VM中 內嵌有兩個JIT編譯器,分別為Client Compiler和Server Compiler,但大多數情況下我們簡稱為C1編譯器和C2編譯器。開發人員可以通過如下命令顯式指定Java虛擬機在運行時到底使用哪一種即時編譯器,如下所示:

  • -client:指定Java, 虛擬機運行在Client模式下,並使用C1編譯器;
    ➢C1編譯器會對字節碼進行簡單和可靠的優化,耗時短。以達到更快的編譯速度。
  • -server:指定Java虛擬機運行在Server模式下,並使用C2編譯器。
    ➢C2進行耗時較長的優化,以及激進優化。但優化的代碼執行效率更高。

C1和C2編譯器不同的優化策略

  • 在不同的編譯器上有不同的優化策略,C1編譯器上主要有方法內聯,去虛擬化、冗余消除。
    ➢方法內聯:將引用的函數代碼編譯到引用點處,這樣可以減少棧幀的生成,減少參數傳遞以及跳轉過程
    ➢去虛擬化:對唯- 的實現類進行內聯
    ➢冗余消除:在運行期間把一些不會 執行的代碼折疊掉

  • C2的優化主要是在全局層面,逃逸分析是優化的基礎。基於逃逸分析在C2.上有如下幾種優化:
    ➢標量替換:用標量值代替聚合對象的屬性值
    ➢棧上分配:對於未逃逸的對象分配對象在棧而不是堆
    ➢同步消除:清除同步操作,通常指synchronized

  • 分層編譯(Tiered Compilation)策略|: 程序解釋執行(不開啟性能監控)可以觸發C1編譯,將字節碼編譯成機器碼,可以進行簡單優化,也可以加上性能監控,C2編譯會根據性能監控信息進行激進優化。

  • 不過在Java7版本之后,一旦開發人員在程序中顯式指定命令“-server"時,默認將會開啟分層編譯策略,由C1編譯器和C2編譯器相互協作共同來執行編譯任務。

小結

  • 一般來講,JIT編譯出來的機器碼性能比解釋器高。
  • C2編譯器啟動時長比C1編譯器慢,
  • 系統穩定執行以后,C2編譯器執行速度遠遠快於C1編譯器。

Graal編譯器與AOT編譯器

Graal編譯器

  • 自JDK10起,HotSpot又加入一個全新的即時編譯器: Graal編譯器。
  • 編譯效果短短幾年時間就追評了C2編譯器。未來可期。
  • 目前,帶着“實驗狀態"標簽,需要使用開關參數
    -XX: +UnlockExperimentalVMOptions -XX: +UseJVMCICompiler去激活,才可以使用。

AOT編譯器

  • jdk9引入了AOT編譯器(靜態提前編譯器,Ahead Of Time Compiler)
  • Java 9引入了實驗性AOT編譯工具jaotc。它借助了Graal 編譯器,將所輸入的Java 類文件轉換為機器碼,並存放至生成的動態共享庫之中。
  • 所謂AOT編譯,是與即時編譯相對立的一個概念。我們知道,即時編譯指的是在程序的運行過程中,將字節碼轉換為可在硬件上直接運行的機器碼,並部署至托管環境中的過程。而AOT 編譯指的則是,在程序運行之前,便將字節碼轉換為機器碼的過程。
    • javac編譯:.java ->.class
    • jaotc編譯:.java ->.so
  • 最大好處: Java虛擬機加載已經預編譯成二進制庫,可以直接執行。不必等待即時編譯器的預熱,減少Java應用給人帶來“第一次運行慢”的不良體驗。
  • 缺點:
    ➢破壞了java"一次編譯,到處運行”,必須為每個不同硬件、OS編譯對應的發行包。
    降低了Java鏈接過程的動態性,加載的代碼在編譯期就必須全部已知。
    ➢還需要繼續優化中,最初只支持Linux x64 java base
    上一篇:關於對象的描述和JVM直接內存區
    下一篇:StringTable


免責聲明!

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



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