JVM(八)執行引擎相關內容

一:兩種解釋器

JAVA位元組碼解釋器:

java位元組碼===》c++代碼==》硬編碼。

首先.java文件編譯成位元組碼,遍歷每行的位元組碼指令,因為每個位元組碼指令的含義都是固定的所以可以根據每行位元組碼指令來轉成c++代碼調用,最後轉成硬編碼(機器碼)來執行。

模板解釋器:

由java位元組碼==》硬編碼。可以從java位元組碼直接到硬編碼。

模板解釋器底層實現流程:

1)申請一塊內存:可讀可寫可執行

2)將處理new位元組碼的硬編碼(舉個例子)拿過來,可以通過解析文件得到

3)將處理new位元組碼的硬編碼寫入申請的內存

4)申請一個函數指針,用這個函數指針執向這塊內存

5)調用的時候,直接通過這個函數指針調用就可以了。

Mac中是無法使用JIT的!因為Mac無法申請一塊可讀可寫可執行的內存塊

二:三種運行模式

-Xint 純位元組碼解釋器

-Xcomp 純模板解釋器

-Xmixed 位元組碼解釋器+模板解釋器

默認是mixed,可以通過java -version查看

混合模式

 

純位元組碼解釋器

 

 

 

 

 純模板解釋器:

 

 

 三:三種運行模式的性能

1:-Xint 純位元組碼解釋器,  解釋一行執行一行,其實屬於解釋執行了。如果代碼多的話肯定效率不會太高。

2:-Xcomp 純模板解釋器,需要把.class編譯成硬編碼之後再執行,如果程序很大,啟動時間耗時可能會較久。

3:-Xmixed 位元組碼解釋器+模板解釋器,所以現在都是混合模式的編譯。剛啟動的時候採用位元組碼解釋器,這樣只要解析執行啟動相關的代碼即可,等到程序運行一段時間之後,即時編譯器收集到的代碼越來越多就可以使用模板解釋器提高運行效率。

2,3那個性能比較高,主要看程序的規模了。

四:模板解釋器使用的硬編碼誰來編譯呢?編譯後又是放在哪裡呢?

即時編譯器:屬於JIT技術。

1)C1即時編譯器。

        c1編譯器是client模式下的即時編譯器。現在64bit機都是server模式了

     (1)觸發條件比C2寬鬆,需要收集的數據較少。

     (2)編譯的優化比較淺比如:基本運算在編譯的時候運算掉了或這final 修飾的字符串的優化。

       (3)c1編譯器編譯生成的代碼執行效率比c2低些。 

       就算對其進行調優,性能的提升曲線也很平緩。

2)C2編譯器

       c2編譯器是server模式下的即時編譯器。

        (1)觸發的條件比較嚴格,一般來說,程序運行一段時間以後觸發。

      (2)優化的比較深。比如編譯的時候判斷操作是否設計到堆棧,如果沒有的話直接優化掉了。

      (3)編譯生成的代碼執行效率比c1更高。

3) 混合編譯

jdk6以前是沒有混合編譯的,後來根據兩種編譯器的使用場景組合起來使用進一步提升性能

 程序運行初期觸發c1編譯器,程序運行一段之後觸發c2編譯器。

這裡說明一點:

位元組碼解釋器是解釋執行的,和即時編譯器無關。

模板解釋器執行的硬編碼就是即時編譯器編譯的。

即時編譯觸發的條件!

     硬編碼在jvm中稱為熱點代碼。

    觸發即時編譯的最小單位不是一個函數,而是一個代碼塊(for,while等)

    client模式下,默認值 1500。即一段代碼執行1500次會觸發即時編譯。

    server模式下,默認值是10000。即一段代碼執行10000次會觸發即時編譯。

熱度衰減: 

    有一段代碼執行了7000次,還有3001次觸發即時編譯,但是在一定時間內,這段代碼沒有被調用,這個次數會以兩倍速遞減變成3500,這時候就需要再執行6501次才會觸發即時編譯。這種就叫熱度衰減。

 


 

補充知識點!!!!!!!

熱機切冷故障。

熱機:就是已經運行了一段時間的機器。

冷機:就是剛運行的機器。

問題:

      當給已經運行了一段時間的熱機集群中增加一個節點冷機的時候,冷機起到負載均衡的作用,但是會出現冷機一上線就會掛掉。

原因:

    熱機中有熱點代碼緩存,抗的並發更大,冷機中一邊運行程序一邊觸發即時編譯佔用cpu。

解決方案:

    冷機切流量,慢慢切,等到觸發即時編譯了就可以正常負載了。


 

熱點代碼緩存區,在方法區。這塊也是調優需要調的地方,但是一般不動。

java -XX:+PrintFlagsFinal -version | grep CodeCacheSize

server 編譯器模式下代碼緩存大小則起始於 2496KB

client 編譯器模式下代碼緩存大小起始於 160KB

五:即時編譯器是如何運行的

即時編譯時通過VM_Thread線程執行的

1:將即時編譯器任務寫入隊列

2:VM_THREAD從這個隊列中讀取任務並執行。

異步執行的。

可以查看編譯線程有多少個?如果有必要可以調整這個大小。

java -XX:+PrintFlagsFinal -version | grep  CICompilerCount

 

六:如何理解java是半解釋半編譯型語言

1:javac 編譯java文件,java運行

2:位元組碼解釋器解釋執行,模板解釋器編譯執行。

七:逃逸分析

逃逸:對象逃逸,直接解釋逃逸不如直接說明不逃逸更直觀。逃逸的話,對於對象如果是共享變量,返回值,參數,則對象是逃逸的,就是逃到了線程外,方法外。

對象不逃逸:對象的作用域是局部變量,對象就是不逃逸的。

 -XX:+DoEscapeAnalysis//開始逃逸分析    -XX:-DoEscapeAnalysis//禁用

基於逃逸分析,JVM開發了三種優化技術:

棧上分配:

逃逸分析開啟,棧上分配就是存在的,逃逸分析默認是開啟的。我們可以測試下看棧上分配的存在。

我們在堆中創建20萬個對象,如果堆里這個對象少於20個,說明有在棧上分配的。前提不會發生GC。

我們先測試只在堆上創建,我把逃逸分析關閉了。

 

 

   public static void main(String[] args) {
        for (int i = 0; i <200000 ; i++) {
           DriverDemo s1= new DriverDemo();
        }
        while (true);
    }

執行之後,我們通過HSDB查看堆中創建了多少個對象。java -cp  sa-jdi.jar sun.jvm.hotspot.HSDB    。首先查找jvm線程。在HSDB中打開線程id.

看到堆里有20萬個對象。說明此時沒有發生棧上分配。

 

 我們把逃逸分析打開再執行查看HSDB。此時堆中對象已經少於20萬個了,所以出現了棧上分配。

標量替換:

 標量:是不可拆分的變量,java中的基本數據類型就是標量的。

scalar replacement。Java中的原始類型無法再分解,可以看作標量(scalar);指向對象的引用也是標量;而對象本身則是聚合量(aggregate),可以包含任意個數的標量。如果把一個Java對象拆散,將其成員變量恢復為分散的變量,這就叫做標量替換。拆散後的變量便可以被單獨分析與優化,可以各自分別在活動記錄(棧幀或寄存器)上分配空間;原本的對象就無需整體分配空間了

聚合量:是可拆分的,就是引用數據類型。

鎖消除: 

 鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除。鎖削除的主要判定依據來源於逃逸分析的數據支持,如果判斷到一段代碼中,在堆上的所有數據都不會逃逸出去被其他線程訪問到,那就可以把它們當作棧上數據對待,認為它們是線程私有的,同步加鎖自然就無須進行。

比如下面這段代碼就會在編譯的時候把同步鎖去掉。

     synchronized (new Object()){
              System.out.println("ddd");
          }

 

Tags: