日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM原理(二)执行引擎篇(JVM程序执行流程、JIT编译器、JIT编译器优化)

發布時間:2023/12/31 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM原理(二)执行引擎篇(JVM程序执行流程、JIT编译器、JIT编译器优化) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、JVM程序執行流程

上一章我們介紹過程序執行通常分為解釋執行和編譯執行,而Java兩種方式都采用了,下面是Java編譯成字節碼、動態編譯和解釋為機器碼的過程分析:

編譯器和解釋器的協調工作流程:

  • 判斷是否是熱點代碼,不是則逐行解釋每條代碼,翻譯成機器碼執行,否則一次性編譯成機器碼存到方法區,以后每次直接運行機器碼

  • JIT優化,可以控制判斷是否是熱點代碼的條件,默認一段代碼(方法、或循環體)被調用10000次以上被認為是熱點代碼

  • JIT編譯的Hot熱點代碼通常是以方法為單位的,最小單位可能是for循環的一段代碼

在部分商用虛擬機中(如HotSpot),Java程序最初是通過解釋器(Interpreter)進行解釋執行的,當虛擬機發現某個方法或代碼塊的運行特別頻繁時,就會把這些代碼認定為“熱點代碼”。為了提高熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關的機器碼,并進行各種層次的優化,完成這個任務的編譯器稱為即時編譯器(Just In Time Compiler,下文統稱JIT編譯器)。

解釋執行:一段代碼,解釋一行執行一行
編譯執行:事先已經編譯成機器碼,直接執行即可,不用解釋

由于Java虛擬機規范并沒有具體的約束規則去限制即時編譯器應該如何實現,所以這部分功能完全是與虛擬機具體實現相關的內容,如無特殊說明,我們提到的編譯器、即時編譯器都是指Hotspot虛擬機內的即時編譯器,虛擬機也是特指HotSpot虛擬機。

我們的JIT是屬于動態編譯方式的,動態編譯(dynamic compilation)指的是“在運行時進行編譯”;與之相對的是事前編譯(ahead-of-time compilation,簡稱AOT),也叫靜態編譯(static compilation)。

JIT編譯(just-in-time compilation)狹義來說是當某段代碼即將第一次被執行時進行編譯,因而叫“即時編譯”。JIT編譯是動態編譯的一種特例。JIT編譯一詞后來被泛化,時常與動態編譯等價;但要注意廣義與狹義的JIT編譯所指的區別。

JIT動態編譯,只是在運行時把某一部分代碼進行動態編譯,不是把所有代碼全部編譯成機器碼,哪些代碼進行動態編譯有一個衡量標準

熱點代碼編譯成機器碼后會保存到方法區

1.1 哪些程序代碼會被即時編譯

程序中的代碼只有是熱點代碼時,才會編譯為本地代碼,那么什么是熱點代碼呢?

運行過程中會被即時編譯器編譯的“熱點代碼”有兩類:

  • 被多次調用的方法。
  • 被多次執行的循環體。
  • 兩種情況,編譯器都是以整個方法作為編譯對象。 這種編譯方法因為編譯發生在方法執行過程之中,因此形象的稱之為棧上替換(On Stack Replacement,OSR),即方法棧幀還在棧上,方法就被替換了。

    1.2 如何判斷熱點代碼呢

    要知道方法或一段代碼是不是熱點代碼,是不是需要觸發即時編譯,需要進行Hot Spot Detection(熱點探測)。

    目前主要的熱點探測方式有以下兩種:

    • 基于采樣的熱點探測
      采用這種方法的虛擬機會周期性地檢查各個線程的棧頂,如果發現某些方法經常出現在棧頂,那這個方法就是“熱點方法”。這種探測方法的好處是實現簡單高效,還可以很容易地獲取方法調用關系(將調用堆棧展開即可),缺點是很難精確地確認一個方法的熱度,容易因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測。
    • 基于計數器的熱點探測
      采用這種方法的虛擬機會為每個方法(甚至是代碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定的閥值,就認為它是“熱點方法”。這種統計方法實現復雜一些,需要為每個方法建立并維護計數器,而且不能直接獲取到方法的調用關系,但是它的統計結果相對更加精確嚴謹。

    1.3 熱點檢測方式

    在HotSpot虛擬機中使用的是第二種——基于計數器的熱點探測方法,因此它為每個方法準備了兩個計數器:方法調用計數器回邊計數器。在確定虛擬機運行參數的前提下,這兩個計數器都有一個確定的閾值,當計數器超過閾值溢出了,就會觸發JIT編譯。

    1.3.1 方法調用計數器

    顧名思義,這個計數器用于統計方法被調用的次數。

    • 在JVM client模式下的閥值是1500次,Server是10 000次。可以通過虛擬機參數: -XX:CompileThreshold設置。
    • 但是JVM還存在熱度衰減,時間段內調用方法的次數較少,計數器就減小。
    • 半衰期:如果計數器在一定時間范圍內始終沒有達到閾值,下一次周期計數開始,計數起始值直接為閾值一半
    • 向編譯器提交編譯請求是異步并行方式

    1.3.2 回邊計數器

    它的作用就是統計一個方法中循環體代碼執行的次數,在字節碼中遇到控制流向后跳轉的指令稱為“回邊”。

    二、理解JIT編譯器

    通過以下問題來了解JIT編譯器

    • 為何HotSpot需要使用解釋器和編譯器并存的架構?
    • JVM為什么要實現兩個不同的即時編譯器?
    • 程序何時會使用解釋器執行?何時會使用編譯器執行?
    • 哪些程序代碼會被編譯成為本地代碼?如何編譯?
    • JAVA代碼的執行效率就一定比C,C++靜態執行的執行差?JAVA代碼解析執行有何優勢?

    2.1 為什么要使用解釋器與編譯器并存的架構

    盡管并不是所有的Java虛擬機都采用解釋器與編譯器并存的架構,但許多主流的商用虛擬機(如HotSpot),都同時包含解釋器和編譯器。

    解釋器沒有編譯成本,編譯需要花時間和空間,雖然只需要編譯一次
    解釋器不需要占用空間,缺點在于,相對于直接執行機器碼需要消耗更多的時間
    空間換時間的思想

    2.1.1 解釋器與編譯器特點

    • 當程序需要迅速啟動和執行的時候,解釋器可以首先發揮作用,省去編譯的時間,立即執行。在程序運行后,隨著時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執行效率
    • 當程序運行環境中內存資源限制較大(如部分嵌入式系統中,安卓),可以使用解釋器執行節約內存,反之可以使用編譯執行來提升效率

    2.1.2 編譯的時間開銷

    解釋器的執行,抽象的看是這樣的:
    輸入的代碼 -> [ 解釋器 解釋執行 ] -> 執行結果

    而要JIT編譯然后再執行的話,抽象的看則是:
    輸入的代碼 -> [ 編譯器 編譯 ] -> 編譯后的代碼 -> [ 執行 ] -> 執行結果

    說JIT比解釋快,其實說的是“執行編譯后的代碼”比“解釋器解釋執行”要快,并不是說“編譯”這個動作比“解釋”這個動作快。JIT編譯再怎么快,至少也比解釋執行一次略慢一些,而要得到最后的執行結果還得再經過一個“執行編譯后的代碼”的過程。所以,對“只執行一次”的代碼而言,解釋執行其實總是比JIT編譯執行要快。

    怎么算是“只執行一次的代碼”呢?粗略說,下面兩個條件同時滿足時就是嚴格的“只執行一次”

    • 1、只被調用一次,例如類的構造器(class initializer())
    • 2、沒有循環

    對只執行一次的代碼做JIT編譯再執行,可以說是得不償失。
    對只執行少量次數的代碼,JIT編譯帶來的執行速度的提升也未必能抵消掉最初編譯帶來的開銷。
    只有對頻繁執行的代碼,JIT編譯才能保證有正面的收益。

    2.1.3 編譯的空間開銷

    對一般的Java方法而言,編譯后代碼的大小相對于字節碼的大小,膨脹比達到10x是很正常的。同上面說的時間開銷一樣,這里的空間開銷也是,只有對執行頻繁的代碼才值得編譯,如果把所有代碼都編譯則會顯著增加代碼所占空間,導致“代碼爆炸”。

    這也就解釋了為什么有些JVM會選擇不總是做JIT編譯,而是選擇用解釋器+JIT編譯器的混合執行引擎。

    2.2 為何要實現兩個不同的即時編譯器

    HotSpot虛擬機中內置了兩個即時編譯器:Client Complier和Server Complier,簡稱為C1、C2編譯器,分別用在客戶端和服務端。

    目前主流的HotSpot虛擬機中默認是采用解釋器與其中一個編譯器直接配合的方式工作。程序使用哪個編譯器,取決于虛擬機運行的模式。HotSpot虛擬機會根據自身版本與宿主機器的硬件性能自動選擇運行模式,用戶也可以使用“-client”或“-server”參數去強制指定虛擬機運行在Client模式或Server模式。

    用Client Complier獲取更高的編譯速度,用Server Complier 來獲取更好的編譯質量(例如 JIT優化)。為什么提供多個即時編譯器與為什么提供多個垃圾收集器類似,都是為了適應不同的應用場景。

    編譯質量的意思就是在編譯過程中會進行優化,Client Complier優化的少,Server Complier優化的多,優化多則啟動慢

    2.3 如何編譯為本地代碼?

    Server Compiler和Client Compiler兩個編譯器的編譯過程是不一樣的。

    對Client Compiler來說,它是一個簡單快速的編譯器,主要關注點在于局部優化,而放棄許多耗時較長的全局優化手段。

    而Server Compiler則是專門面向服務器端的,并為服務端的性能配置特別調整過的編譯器,是一個充分優化過的高級編譯器。

    三、JIT優化

    HotSpot 虛擬機使用了很多種優化技術,這里只簡單介紹其中的幾種,完整的優化技術介紹可以參考官網內容。

    JIT編譯優化,不是在javac命令編譯優化,是在jvm運行期間的優化策略

    3.1 公共子表達式的消除

    公共子表達式消除是一個普遍應用于各種編譯器的經典優化技術,他的含義是:如果一個表達式E已經計算過了,并且從先前的計算到現在E中所有變量的值都沒有發生變化,那么E的這次出現就成為了公共子表達式。對于這種表達式,沒有必要花時間再對他進行計算,只需要直接用前面計算過的表達式結果代替E就可以了。

    • 如果這種優化僅限于程序的基本塊內,便稱為局部公共子表達式消除(Local Common Subexpression Elimination)
    • 如果這種優化范圍涵蓋了多個基本塊,那就稱為全局公共子表達式消除(Global Common Subexpression Elimination)。

    舉個簡單的例子來說明他的優化過程,假設存在如下代碼:

    int d = (c*b)*12+a+(a+b*c);

    注意只有多次調用發現c和b都是一個值才會把b*c的結果緩存

    如果這段代碼交給Javac編譯器不會進行任何優化,那生成的代碼如下所示,是完全遵照Java源碼的寫法直譯而成的。

    iload_2 // b imul // 計算b*c bipush 12 // 推入12 imul // 計算(c*b)*12 iload_1 // a iadd // 計算(c*b)*12+a iload_1 // a iload_2 // b iload_3 // c imul // 計算b*c iadd // 計算a+b*c iadd // 計算(c*b)*12+a+(a+b*c) istore 4

    當這段代碼進入到虛擬機即時編譯器后,他將進行如下優化:編譯器檢測到”cb“與”bc“是一樣的表達式,而且在計算期間b與c的值是不變的。因此,這條表達式就可能被視為:

    int d = E*12+a+(a+E);

    這時,編譯器還可能(取決于哪種虛擬機的編譯器以及具體的上下文而定)進行另外一種優化:代數化簡(Algebraic Simplification),把表達式變為:

    int d = E*13+a*2;

    表達式進行變換之后,再計算起來就可以節省一些時間了。

    由于E已經是被計算之后的結果,因此可以直接替換成一個常量值

    變量和常量的操作不同,常量可以直接壓入到局部變量表,而變量先要取到操作棧,再從操作棧取出來存儲到局部變量表

    3.2 方法內聯

    JVM中如何執行方法:

    • 結構:方法區中class對象的方法在class文件加載到方法區時將符號引用解析為直接引用,指向method對象(Field對象類似),method對象除了有自己的地址外,有一個code區專門存放要執行的字節碼指令
    • 執行流程:線程執行方法的時候,會先讀取class對象信息,再從class信息獲取到要執行的方法地址,然后把方法形成棧幀壓入到棧,方法執行過程中內部如果又調用其他方法以此類推將需要執行的方法壓入棧里,棧頂的方法就是正在執行的方法,一旦某一個方法執行完了就會彈棧

    如果方法調用很多的話,方法入棧出棧的操作也會很多,棧會很深,對應性能損耗也很多

    在使用JIT進行即時編譯時,將方法調用直接使用方法體中的代碼進行替換,這就是方法內聯,減少了方法調用過程中壓棧與入棧的開銷。同時為之后的一些優化手段提供條件。如果JVM監測到一些小方法被頻繁的執行,它會把方法的調用替換成方法體本身。

    比如說下面這個:

    private int add4(int x1, int x2, int x3, int x4) {return add2(x1, x2) + add2(x3, x4); } private int add2(int x1, int x2) {return x1 + x2; }

    可以肯定的是運行一段時間后JVM會把add2方法去掉,并把你的代碼翻譯成:

    private int add4(int x1, int x2, int x3, int x4) {return x1 + x2 + x3 + x4; }

    可以有效防止棧溢出,以及方法調用過程中壓棧與入棧的開銷

    3.3 逃逸分析

    逃逸分析(Escape Analysis)是目前Java虛擬機中比較前沿的優化技術。這是一種可以有效減少Java程序中同步負載內存堆分配壓力的跨函數全局數據流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用范圍從而決定是否要將這個對象分配到堆上。

    同步負載:指的是加鎖synchronized的壓力
    內存堆分配:堆里面分配對象

    算法具體實現不用深究,只要知道逃逸分析能夠帶來的效果即可。

    逃逸分析的基本行為就是分析對象動態作用域:當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其他地方中,稱為方法逃逸。

    對象不能被外部訪問,則沒有逃逸,沒有逃逸可以進行一系列優化,主要有三種優化策略,即后面講的三種都是建立在逃逸分析之后的結論進行優化的

    逃逸分析包括:

    • 全局變量賦值逃逸
    • 方法返回值逃逸
    • 實例引用發生逃逸
    • 線程逃逸:賦值給類變量或可以在其他線程中訪問的實例變量

    例如:

    public class EscapeAnalysis {//全局變量public static Object object;public void globalVariableEscape(){//全局變量賦值逃逸,即全局變量能夠被外部訪問object = new Object();}public Object methodEscape(){ //方法返回值逃逸,即返回值能夠被外部訪問return new Object();}public void instancePassEscape(){ //實例引用發生逃逸,即實例引用能夠被外部訪問this.speak(this);}public void speak(EscapeAnalysis escapeAnalysis){System.out.println("Escape Hello");} }

    只要一個方法里面的對象,它的生命周期沒有超過方法范圍,則稱為該對象沒有逃逸出這個方法

    • 使用方法逃逸的案例進行分析:

      public static StringBuffer craeteStringBuffer(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb; }

      StringBuffer sb是一個方法內部變量,上述代碼中直接將sb返回,這樣這個StringBuffer有可能被其他方法所改變,這樣它的作用域就不只是在方法內部,雖然它是一個局部變量,稱其逃逸到了方法外部。甚至還有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量,稱為線程逃逸。

      上述代碼如果想要StringBuffer sb不逃出方法,可以這樣寫:

      public static String createStringBuffer(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString(); }

      不直接返回 StringBuffer,那么StringBuffer將不會逃逸出方法。

    使用逃逸分析,編譯器可以對代碼做如下優化:

    • 一、同步省略。如果一個對象被發現只能從一個線程被訪問到,那么對于這個對象的操作可以不考慮同步。
    • 二、將堆內存分配轉化為棧內存分配。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸,對象可能是棧分配的候選,而不是堆分配。
    • 三、分離對象或標量替換。有的對象可能不需要作為一個連續的內存結構存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內存,而是存儲在CPU寄存器中。

    在Java代碼運行時,通過JVM參數可指定是否開啟逃逸分析

    -XX:+DoEscapeAnalysis : 表示開啟逃逸分析
    -XX:-DoEscapeAnalysis : 表示關閉逃逸分析

    從jdk 1.7開始已經默認開啟逃逸分析,如需關閉,需要指定 -XX:-DoEscapeAnalysis

    3.4 對象的棧上內存分配

    我們知道,在一般情況下,對象和數組元素的內存分配是在堆內存上進行的。但是隨著JIT編譯器的日漸成熟,很多優化使這種分配策略并不絕對。JIT編譯器就可以在編譯期間根據逃逸分析的結果,來決定是否可以將對象的內存分配從堆轉化為棧。

    我們來看以下代碼:

    public class EscapeAnalysisTest {public static void main(String[] args) {long a1 = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {alloc();}// 查看執行時間long a2 = System.currentTimeMillis();System.out.println("cost " + (a2 - a1) + " ms");// 為了方便查看堆內存中對象個數,線程sleeptry {Thread.sleep(100000);} catch (InterruptedException e1) {e1.printStackTrace();}}// 此方法內的User對象,未發生逃逸private static void alloc() {User user = new User();}static class User {} }

    其實代碼內容很簡單,就是使用for循環,在代碼中創建100萬個User對象。

    我們在alloc方法中定義了User對象,但是并沒有在方法外部引用他。也就是說,這個對象并不會逃逸到alloc外部。經過JIT的逃逸分析之后,就可以對其內存分配進行優化。

    我們指定以下JVM參數并運行:

    -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

    在程序打印出 cost XX ms 后,代碼運行結束之前,我們使用jmap命令,來查看下當前堆內存中有多少個User對象:

    ~ jps 2809 StackAllocTest 2810 Jps ~ jmap -histo 2809 num #instances #bytes class name ---------------------------------------------- 1: 524 87282184 [I 2: 1000000 16000000 StackAllocTest$User 3: 6806 2093136 [B 4: 8006 1320872 [C 5: 4188 100512 java.lang.String 6: 581 66304 java.lang.Class
    • 從上面的jmap執行結果中我們可以看到,堆中共創建了100萬個 StackAllocTest$User 實例。
    • 在關閉逃避分析的情況下(-XX:-DoEscapeAnalysis),雖然在alloc方法中創建的User對象并沒有逃逸到方法外部,但是還是被分配在堆內存中。也就說,如果沒有JIT編譯器優化,沒有逃逸分析技術,正常情況下就應該是這樣的。即所有對象都分配到堆內存中。

    接下來,我們開啟逃逸分析,再來執行下以上代碼。

    -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

    在程序打印出 cost XX ms 后,代碼運行結束之前,我們使用 jmap 命令,來查看下當前堆內存中有多少個User對象:

    ~ jps 709 2858 Launcher 2859 StackAllocTest 2860 Jps ~ jmap -histo 2859 num #instances #bytes class name --------------------------------------------- 1: 524 101944280 [I 2: 6806 2093136 [B 3: 83619 1337904 StackAllocTest$User 4: 8006 1320872 [C 5: 4188 100512 java.lang.String 6: 581 66304 java.lang.Class
    • 從以上打印結果中可以發現,開啟了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆內存中只有8萬多個 StackAllocTest$User 對象。也就是說在經過JIT優化之后,堆內存中分配的對象數量,從100萬降到了8萬。
    • 除了以上通過jmap驗證對象個數的方法以外,還可以嘗試將堆內存調小,然后執行以上代碼,根據GC的次數來分析,也能發現,開啟了逃逸分析之后,在運行期間,GC次數會明顯減少。正是因為很多堆上分配被優化成了棧上分配,所以GC次數有了明顯的減少。

    并不是完全都在棧上內存分配,因為棧上內存分配也有可能把棧內存空間撐滿了

    另外逃逸分析是建立在JIT編譯的時候,而JIT編譯只會針對熱點代碼,默認執行1W次以上的方法或循環體

    總結
    所以,如果以后再有人問你:是不是所有的對象和數組都會在堆內存分配空間?

    那么你可以告訴他:不一定,隨著JIT編譯器的發展,在編譯期間,如果JIT經過逃逸分析,發現有些對象沒有逃逸出方法,那么有可能堆內存分配會被優化成棧內存分配。但是這也并不是絕對的。就像我們前面看到的一樣,在開啟逃逸分析之后,也并不是所有User對象都沒有在堆上分配。

    3.5 標量替換

    標量(Scalar)是指一個無法再分解成更小的數據的數據 。

    標量就是 基本數據類型 和 引用類型,相反就是聚集量,指對象
    標量替換的意思就是將對象拆解成多個標量進行存儲

    好處,首先看下對象的布局,對象在堆里面有三塊布局區域:

    • 對象頭
    • 實例數據區域
    • 對齊填充

    我們可能真正需要的只有實例數據區域的數據,但是另外兩塊也占據內存,但是都沒用到 對象用被標量替換后,就相當于只需有實例空間內存區域就行了,而且這塊內存區域也不在堆里面 而在棧里面,減輕gc回收壓力

    在JIT階段,如果經過逃逸分析,發現一個對象不會被外界訪問的話,那么經過JIT優化,就會把這個對象拆解成若干個其中包含的若干個成員變量來代替。

    //有一個類A public class A{public int a=1;public int b=2 } //方法getAB使用類A里面的a,b private void getAB(){A x = new A();x.a;x.b; } //JVM在編譯的時候會直接編譯成 private void getAB(){a = 1;b = 2; }

    這就是標量替換

    有效降低內存消耗

    3.6 同步鎖消除

    同樣基于逃逸分析,當加鎖的變量不會發生逃逸,是線程私有的完全沒有必要加鎖。在JIT編譯時期就可以將同步鎖去掉,以減少加鎖與解鎖造成的資源開銷。

    public class TestLockEliminate {public static String getString(String s1, String s2) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);return sb.toString();}public static void main(String[] args) {long tsStart = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {getString("TestLockEliminate ""Suffix");}System.out.println("一共耗費:" + (System.currentTimeMillis() - tsStart) + " ms");} }

    getString()方法中的StringBuffer是函數內部的局部變量,作用于方法內部,不可能逃逸出該方法,因此他就不可能被多個線程同時訪問,也就沒有資源的競爭,但是StringBuffer的append操作卻需要執行同步操作,代碼如下:

    @Override public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this; }

    逃逸分析和鎖消除分別可以使用參數 -XX:+DoEscapeAnalysis 和 -XX:+EliminateLocks (鎖消除必須在-server模式下)開啟。使用如下參數運行上面的程序:

    -XX:+DoEscapeAnalysis -XX:-EliminateLocks

    得到如下結果:

    使用如下命令運行程序:

    -XX:+DoEscapeAnalysis -XX:+EliminateLocks

    得到如下結果:

    總結

    以上是生活随笔為你收集整理的JVM原理(二)执行引擎篇(JVM程序执行流程、JIT编译器、JIT编译器优化)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。