1. 程式人生 > 實用技巧 >JVM-09-執行引擎

JVM-09-執行引擎

概述

在這裡插入圖片描述

  • 執行引擎是Java虛擬機器核心的組成部分之一
  • 虛擬機器是一個相對於物理機的概念,這兩種機器都有程式碼執行能力,其區別是物理機的執行引擎是直接建立在處理器、快取、指令集和作業系統層面上的,而虛擬機器的執行引擎則是由軟體自行實現 的,因此可以不受物理條件制約的定製指令集與執行引擎的結構體系,能夠執行那些不被硬體直接支援的指令集格式
  • JVM的主要任務是負責裝載位元組碼到其內部,但位元組碼並不能夠直接執行在作業系統之上,因為位元組碼指令並非等價本地機器指令,它內部包含的僅僅只是一些 能夠被jvm識別的位元組碼指令,符號表,以及其他輔助資訊
  • 執行引擎的主要任務就是將位元組碼指令解釋\編譯為對應平臺的本地機器指令,簡單來說,jvm中的執行引擎充當了將高階語言翻譯為機器語言的譯者

執行引擎的工作過程

在這裡插入圖片描述

Java程式碼編譯和執行過程

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

  • 什麼是直譯器,JIT編譯器
    • 直譯器:當Java虛擬機器啟動時會根據預定義的規範對位元組碼採用逐行解釋的方式執行,將每條位元組碼檔案中的內容翻譯為對應平臺的本地機器指令執行
    • JIT(Just In Time Compiler)編譯器:就是虛擬機器將原始碼直接編譯成和本地機器平臺相關的機器語言
  • 為什麼說Java是半編譯半解釋型語言

在這裡插入圖片描述

機器碼、指令、彙編

在這裡插入圖片描述

機器碼

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

指令

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

指令集

  • 不同的硬體平臺,各自支援的指令是由差別的,因此每個平臺所支援的指令,稱之為對應平臺的指令集
  • 如:
    • x86指令集,對應的是x86架構的平臺
    • ARM指令集,對應的是ARM架構的平臺

彙編

  • 由於指令的可讀性還是太差,於是人們又發明了組合語言
  • 在組合語言中,用助記符代替機器指令的操作碼,用地址符號或標號代替指令或運算元的地址
  • 在不同的硬體平臺,組合語言對應著不同的機器語言指令集,通過彙編過程轉換成機器指令
    • 由於計算機只認識指令碼,所以用匯編語言編寫的程式必須翻譯成機器指令碼,計算機才能識別和執行

高階語言

  • 為了使計算機使用者程式設計更加容易,後來就出現了各種高階計算機語言,高階語言比機器語言,組合語言更接近人的語言
  • 當計算機執行高階語言編寫的程式時,仍然需要把程式解釋和編譯成機器的指令碼,完成這個過程的程式就叫做解釋程式或編譯程式

在這裡插入圖片描述

C/C++源程式執行過程

在這裡插入圖片描述

  • 編譯過程又可以分為兩個階段,編譯和彙編
    • 編譯過程:是讀取源程式,對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的彙編程式碼
    • 彙編過程:實際上指把組合語言程式碼翻譯成目標機器指令的過程

位元組碼

  • 位元組碼是一種中間狀態(中間碼)的二進位制程式碼(檔案),他比機器碼更抽象,需要直譯器轉譯後才能成為機器碼
  • 位元組碼主要為了實現特定軟體執行環境和軟體環境,與硬體環境無關
  • 位元組碼的實現方式是通過編譯器和虛擬機器器,編譯器將原始碼編譯成位元組碼,特定平臺上的虛擬機器器將位元組碼轉譯為可以直接執行的指令,
    • 位元組碼的典型應用為Java bytecode

在這裡插入圖片描述

直譯器

直譯器工作機制

  • 直譯器真正意義上所承擔的角色就是一個執行時“翻譯者”,將位元組碼檔案中的內容“翻譯”為對應平臺的本地機器指令執行
  • 當一條位元組碼指令被解釋執行完成後,接著再根據PC暫存器中記錄的下一條需要被執行的位元組碼指令執行解釋操作

直譯器分類

在Java的發展歷史裡,一共有兩套解釋執行器,即古老的位元組碼直譯器,現在普遍使用的模板直譯器

  • 位元組碼直譯器在執行時通過純軟體程式碼模擬位元組碼的執行,效率非常低下
  • 模板直譯器將每一條位元組碼和一個模板函式相關聯,模板函式中直接產生這條位元組碼執行時的機器碼,從而很大程度上提高了直譯器的效能
    • 在Hotspot中,直譯器主要由interpreter模組和Code模組構成,
      • Interpreter模組:實現瞭解釋器的核心功能
      • Code模組:用於管理Hotspot在執行時生成的本地機器指令

在這裡插入圖片描述

JIT編譯器

Java程式碼的執行分類

  • 第一種是將原始碼編譯成位元組碼檔案,然後在執行時通過直譯器將位元組碼檔案轉為機器碼執行
  • 第二種是編譯執行(直接編譯成機器碼),現代虛擬機器為了提高執行效率,會使用及時編譯技術將方法編譯成機器碼後再執行

為什麼Hotspot中已經內建了JIT編譯器了還要保留直譯器

在這裡插入圖片描述

Hotspot JVM的執行方式

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

JIT編譯器

  • 概念解釋
    • Java語言的編譯期其實是一段不確定的操作過程,因為他可能是指一個前端編譯器把.java檔案轉變成.class檔案的過程
    • 也可能是指虛擬機器的後端執行期編譯器把位元組碼轉變成機器碼的過程
    • 還可能是指使用靜態提前編譯器(AOT編譯器)直接把.java檔案編譯成本地機器程式碼的過程

在這裡插入圖片描述

熱點程式碼及探測方式

是否需要啟動JIT編譯器將位元組碼直接編譯為對應平臺的本地機器指令,則需要根據程式碼被呼叫執行的頻率而定,關於那需要被編譯為原生代碼的位元組碼,也被稱之為熱點程式碼,JIT編譯器在執行時會針對那些頻繁被呼叫的熱點程式碼作出深度優化,將其直接編譯為對應平臺的本地機器指令,以此提升程式的執行效能

  • 一個被多次呼叫的方法,或者是一個方法體內部迴圈次數較多的迴圈體都可以被稱之為熱點程式碼,以此都可以通過JIT編譯器編譯為本地機器指令,由於這種編譯方式發生在方法的執行過程中,因此也被稱之為棧上替換,或簡稱為OSR(On Stack Replacement)編譯
  • 一個方法究竟要被呼叫多少次,或者一個迴圈體究竟要執行多少次迴圈才可以達到這個標準,必然需要一個明確的閾值,JIT編譯器才會將這些熱點程式碼編譯為本地機器指令執行,這裡主要依靠熱點探測功能
  • 目前Hotspot所採用的熱點探測方式是基於計數器的熱點探測
  • 採用基於計數器的熱點探測,Hotspot將會為每一個方法都建立兩個不同型別的計數器,分別為方法呼叫計數器,和回邊計數器
    • 方法呼叫計數器用於統計方法的呼叫次數
    • 回邊計數器則用於統計迴圈體執行的迴圈次數

方法呼叫計數器

  • 方法呼叫計數器就用於統計方法的呼叫次數,預設閾值在Client模式下是1500次,在Server模式下是10000次,超過這個閾值,就會觸發JIT編譯
  • 可以通過-XX:CompileThreshold來設定
  • 當一個方法被呼叫時,會先檢查該方法引數是否存在被JIT編譯過的版本,如果存在,優先使用編譯後的原生代碼來執行,如果不存在已被編譯過的版本,則將此方法的呼叫計數器值+1,然後判斷方法呼叫計數器與回邊計數器之和是否超過方法呼叫計數器的閾值。如果已超過閾值,那麼將會向即時編譯器提交一個該方法的程式碼編譯請求

在這裡插入圖片描述

回邊計數器

作用是統計一個方法中迴圈體程式碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令稱為回邊,顯然,建立回邊計數器統計的目的就是為了觸發OSR編譯

在這裡插入圖片描述

熱度衰減

  • 如果不做任何設定,方法呼叫計數器統計的並不是方法被呼叫的絕對次數,而是一個相對的執行頻率,即一段時間之內方法被呼叫的次數,當超過一定從時間限度,如果方法的呼叫次數仍然不足以讓他提交給即時編譯器,那這個方法的呼叫計數器就會被減少一半,這個過程被稱之為方法呼叫計數器熱度的衰減,而這段時間就稱為此方法統計的半衰週期
  • 進行熱度衰減的動作是在虛擬機器進行垃圾收集時順便進行的,可以使用-XX:-UseCounterDecay來關閉熱端衰減,讓方法計數器統計方法呼叫的絕對次數,這樣,只要系統執行時間足夠長,絕大部分方法都會被編譯成原生代碼
  • 另外,可以使用-XX:CounterHalfLifeTime來修改半衰週期的時間,單位是秒

使用命令切換編譯模式

  • -Xint 只使用直譯器
  • -Xcomp 只使用編譯器
  • -Xmixed 使用混合模式

JIT分類

在Hotspot中內嵌有兩個JIT編譯器,分別為Client Compiler和 Server Compiler,簡稱為C1,C2編譯器

  • 使用-client 使用c1編譯器
    • c1編譯器會對位元組碼進行簡單和可靠的優化,耗時短,已達到更快的編譯速度
  • 使用-server使用c2編譯器
    • c2進行耗時長的優化,以及激進優化,但優化的程式碼執行效率更高

優化策略

  • c1編譯器
    • 方法內聯:將引用的函式程式碼編譯到引用點處,這樣可以減少棧幀的生成,減少引數傳遞以及跳轉過程
    • 去虛擬化:對唯一的實現類進行內聯
    • 冗餘消除:在執行期間把一些不會執行的程式碼摺疊掉
  • c2編譯器的優化主要是在全域性層面,逃逸分析是優化的基礎
    • 標量替換:用標量值代替聚合物件的屬性值
    • 棧上分配:對於未逃逸的物件分配物件在棧而不是堆
    • 同步消除:清除同步操作,通常指syncchronized

分層編譯策略

在這裡插入圖片描述

一般來說,JIT編譯出來的機器碼效能比直譯器高

c2編譯器啟動時長比c1慢,系統穩定執行後,c2執行速度遠遠快於c1

補充

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述