日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

JVM 解釋和編譯指南

來源: 責編: 時間:2023-11-06 17:18:37 253觀看
導讀Java 是一種跨平臺的編程語言。程序源代碼會被編譯為 字節碼bytecode,然后字節碼在運行時被轉換為 機器碼machine code。解釋器interpreter 在物理機器上模擬出的抽象計算機上執行字節碼指令。即時just-in-time(JIT)編譯

ttn28資訊網——每日最新資訊28at.com

Java 是一種跨平臺的編程語言。程序源代碼會被編譯為 字節碼bytecode,然后字節碼在運行時被轉換為 機器碼machine code。解釋器interpreter 在物理機器上模擬出的抽象計算機上執行字節碼指令。即時just-in-time(JIT)編譯發生在運行期,而 預先ahead-of-time(AOT)編譯發生在構建期。ttn28資訊網——每日最新資訊28at.com

本文將說明解釋器、JIT 和 AOT 分別何時起作用,以及如何在 JIT 和 AOT 之間權衡。ttn28資訊網——每日最新資訊28at.com

源代碼、字節碼、機器碼

應用程序通常是由 C、C++ 或 Java 等編程語言編寫。用這些高級編程語言編寫的指令集合稱為源代碼。源代碼是人類可讀的。要在目標機器上執行它,需要將源代碼轉換為機器可讀的機器碼。這個轉換工作通常是由 編譯器compilerttn28資訊網——每日最新資訊28at.com

然而,在 Java 中,源代碼首先被轉換為一種中間形式,稱為字節碼。字節碼是平臺無關的,所以 Java 被稱為平臺無關編程語言。Java 編譯器 javac 將源代碼轉換為字節碼。然后解釋器解釋執行字節碼。ttn28資訊網——每日最新資訊28at.com

下面是一個簡單的 Java 程序, Hello.javattn28資訊網——每日最新資訊28at.com

//Hello.javapublic class Hello {    public static void main(String[] args) {         System.out.println("Inside Hello World!");    }}

使用 javac 編譯它,生成包含字節碼的 Hello.class 文件。ttn28資訊網——每日最新資訊28at.com

$ javac Hello.java$ lsHello.class  Hello.java

現在,使用 javap 來反匯編 Hello.class 文件的內容。使用 javap 時如果不指定任何選項,它將打印基本信息,包括編譯這個 .class 文件的源文件、包名稱、公共和受保護字段以及類的方法。ttn28資訊網——每日最新資訊28at.com

$ javap Hello.classCompiled from "Hello.java"public class Hello {    public Hello();    public static void main(java.lang.String[]);}

要查看 .class 文件中的字節碼內容,使用 -c 選項:ttn28資訊網——每日最新資訊28at.com

$ javap -c Hello.classCompiled from "Hello.java"public class Hello {  public Hello();        Code:           0: aload_0           1: invokespecial #1                      // Method java/lang/Object."<init>":()V           4: return  public static void main(java.lang.String[]);        Code:           0: getstatic         #2                      // Field java/lang/System.out:Ljava/io/PrintStream;           3: ldc               #3                      // String Inside Hello World!           5: invokevirtual #4                      // Method    java/io/PrintStream.println:(Ljava/lang/String;)V           8: return}

要獲取更詳細的信息,使用 -v 選項:ttn28資訊網——每日最新資訊28at.com

$ javap -v Hello.class

解釋器,JIT 和 AOT

解釋器負責在物理機器上模擬出的抽象計算機上執行字節碼指令。當使用 javac 編譯源代碼,然后使用 java 執行時,解釋器在程序運行時運行并完成它的目標。ttn28資訊網——每日最新資訊28at.com

$ javac Hello.java$ java HelloInside Hello World!

JIT 編譯器也在運行期發揮作用。當解釋器解釋 Java 程序時,另一個稱為運行時 分析器profiler 的組件將靜默地監視程序的執行,統計各部分代碼被解釋的次數。基于這些統計信息可以檢測出程序的 熱點hotspot,即那些經常被解釋的代碼。一旦代碼被解釋次數超過設定的閾值,它們滿足被 JIT 編譯器直接轉換為機器碼的條件。所以 JIT 編譯器也被稱為分析優化的編譯器。從字節碼到機器碼的轉換是在程序運行過程中進行的,因此稱為即時編譯。JIT 減少了解釋器將同一組指令模擬為機器碼的負擔。ttn28資訊網——每日最新資訊28at.com

AOT 編譯器在構建期編譯代碼。在構建時將需要頻繁解釋和 JIT 編譯的代碼直接編譯為機器碼可以縮短 Java 虛擬機Java Virtual Machine(JVM) 的預熱warm-up時間。(LCTT 譯注:Java 程序啟動后首先字節碼被解釋執行,此時執行效率較低。等到程序運行了足夠的時間后,代碼熱點被檢測出來,JIT 開始發揮作用,程序運行效率提升。JIT 發揮作用之前的過程就是預熱。)AOT 是在 Java 9 中引入的一個實驗性特性。jaotc 使用 Graal 編譯器(它本身也是用 Java 編寫的)來實現 AOT 編譯。ttn28資訊網——每日最新資訊28at.com

以 Hello.java 為例:ttn28資訊網——每日最新資訊28at.com

//Hello.javapublic class Hello {    public static void main(String[] args) {        System.out.println("Inside Hello World!");    }}$ javac Hello.java$ jaotc --output libHello.so Hello.class$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libHello.so HelloInside Hello World!

解釋和編譯發生的時機

下面通過例子來展示 Java 在什么時候使用解釋器,以及 JIT 和 AOT 何時參與進來。這里有一個簡單的程序 Demo.java :ttn28資訊網——每日最新資訊28at.com

//Demo.javapublic class Demo {    public int square(int i) throws Exception {        return(i*i);    }    public static void main(String[] args) throws Exception {        for (int i = 1; i <= 10; i++) {            System.out.println("call " + Integer.valueOf(i));            long a = System.nanoTime();            Int r = new Demo().square(i);            System.out.println("Square(i) = " + r);            long b = System.nanoTime();            System.out.println("elapsed= " + (b-a));            System.out.println("--------------------------------");        }    }}

在這個程序的 main() 方法中創建了一個 Demo 對象的實例,并調用該實例的 square()方法,然后顯示 for 循環迭代變量的平方值。編譯并運行它:ttn28資訊網——每日最新資訊28at.com

$ javac Demo.java$ java Demo1 iterationSquare(i) = 1Time taken= 8432439--------------------------------2 iterationSquare(i) = 4Time taken= 54631--------------------------------...--------------------------------10 iterationSquare(i) = 100Time taken= 66498--------------------------------

上面的結果是由誰產生的呢?是解釋器,JIT 還是 AOT?在目前的情況下,它完全是通過解釋產生的。我是怎么得出這個結論的呢?只有代碼被解釋的次數必須超過某個閾值時,這些熱點代碼片段才會被加入 JIT 編譯隊列。只有這時,JIT 編譯才會發揮作用。使用以下命令查看 JDK 11 中的該閾值:ttn28資訊網——每日最新資訊28at.com

$ java -XX:+PrintFlagsFinal -version | grep CompileThreshold intx CompileThreshold     = 10000                                      {pd product} {default}[...]openjdk version "11.0.13" 2021-10-19OpenJDK Runtime Environment 18.9 (build 11.0.13+8)OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8, mixed mode, sharing)

上面的輸出表明,一段代碼被解釋 10,000 次才符合 JIT 編譯的條件。這個閾值是否可以手動調整呢?是否有 JVM 標志可以指示出方法是否被 JIT 編譯了呢?答案是肯定的,而且有多種方式可以達到這個目的。ttn28資訊網——每日最新資訊28at.com

使用 -XX:+PrintCompilation 選項可以查看一個方法是否被 JIT 編譯。除此之外,使用 -Xbatch 標志可以提高輸出的可讀性。如果解釋和 JIT 同時發生,-Xbatch 可以幫助區分兩者的輸出。使用這些標志如下:ttn28資訊網——每日最新資訊28at.com

$ java -Xbatch  -XX:+PrintCompilation  Demo         34        1        b  3           java.util.concurrent.ConcurrentHashMap::tabAt (22 bytes)         35        2         n 0           jdk.internal.misc.Unsafe::getObjectVolatile (native)            35        3        b  3           java.lang.Object::<init> (1 bytes)[...]        210  269         n 0           java.lang.reflect.Array::newArray (native)   (static)        211  270        b  3           java.lang.String::substring (58 bytes)[...]--------------------------------10 iterationSquare(i) = 100Time taken= 50150--------------------------------

注意,上面命令的實際輸出太長了,這里我只是截取了一部分。輸出很長的原因是除了 Demo 程序的代碼外,JDK 內部類的函數也被編譯了。由于我的重點是 Demo.java 代碼,我希望排除內部包的函數來簡化輸出。通過選項 -XX:CompileCommandFile 可以禁用內部類的 JIT:ttn28資訊網——每日最新資訊28at.com

$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler Demo

在選項 -XX:CompileCommandFile 指定的文件 hotspot_compiler 中包含了要排除的包:ttn28資訊網——每日最新資訊28at.com

$ cat hotspot_compilerquietexclude java/* *exclude jdk/* *exclude sun/* *

第一行的 quiet 告訴 JVM 不要輸出任何關于被排除類的內容。用 -XX:CompileThreshold 將 JIT 閾值設置為 5。這意味著在解釋 5 次之后,就會進行 JIT 編譯:ttn28資訊網——每日最新資訊28at.com

$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler /-XX:CompileThreshold=5 Demo        47      1       n 0     java.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)              (static)        47      2       n 0     java.lang.invoke.MethodHandle::invokeBasic(LLLLL)L (native)           47      3       n 0     java.lang.invoke.MethodHandle::linkToSpecial(LLLLLLL)L (native)              (static)        48      4       n 0     java.lang.invoke.MethodHandle::linkToStatic(L)I (native)   (static)        48      5       n 0     java.lang.invoke.MethodHandle::invokeBasic()I (native)           48      6       n 0     java.lang.invoke.MethodHandle::linkToSpecial(LL)I (native)              (static)[...]        1 iteration        69   40         n 0     java.lang.invoke.MethodHandle::linkToStatic(ILIIL)I (native)              (static)[...]Square(i) = 1        78   48         n 0     java.lang.invoke.MethodHandle::linkToStatic(ILIJL)I (native)   (static)        79   49         n 0     java.lang.invoke.MethodHandle::invokeBasic(ILIJ)I (native)   [...]        86   54         n 0     java.lang.invoke.MethodHandle::invokeBasic(J)L (native)           87   55         n 0     java.lang.invoke.MethodHandle::linkToSpecial(LJL)L (native)   (static)Time taken= 8962738--------------------------------2 iterationSquare(i) = 4Time taken= 26759--------------------------------10 iterationSquare(i) = 100Time taken= 26492--------------------------------

好像輸出結果跟只用解釋時并沒有什么區別。根據 Oracle 的文檔,這是因為只有禁用 TieredCompilation 時 -XX:CompileThreshold 才會生效:ttn28資訊網——每日最新資訊28at.com

$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler /-XX:-TieredCompilation -XX:CompileThreshold=5 Demo124     1       n       java.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)   (static)127     2       n       java.lang.invoke.MethodHandle::invokeBasic(LLLLL)L (native)   [...]1 iteration        187   40        n       java.lang.invoke.MethodHandle::linkToStatic(ILIIL)I (native)   (static)[...](native)   (static)        212   54        n       java.lang.invoke.MethodHandle::invokeBasic(J)L (native)           212   55        n       java.lang.invoke.MethodHandle::linkToSpecial(LJL)L (native)   (static)Time taken= 12337415[...]--------------------------------4 iterationSquare(i) = 16Time taken= 37183--------------------------------5 iteration        214   56        b       Demo::<init> (5 bytes)        215   57        b       Demo::square (16 bytes)Square(i) = 25Time taken= 983002--------------------------------6 iterationSquare(i) = 36Time taken= 81589[...]10 iterationSquare(i) = 100Time taken= 52393

可以看到在第五次迭代之后,代碼片段被 JIT 編譯了:ttn28資訊網——每日最新資訊28at.com

--------------------------------5 iteration        214   56        b       Demo::<init> (5 bytes)        215   57        b       Demo::square (16 bytes)Square(i) = 25Time taken= 983002--------------------------------

可以看到,與 square() 方法一起,構造方法也被 JIT 編譯了。在 for 循環中調用 square() 之前要先構造 Demo 實例,所以構造方法的解釋次數同樣達到 JIT 編譯閾值。這個例子說明了在解釋發生之后何時 JIT 會介入。ttn28資訊網——每日最新資訊28at.com

要查看編譯后的代碼,需要使用 -XX:+PrintAssembly 標志,該標志僅在庫路徑中有反匯編器時才起作用。對于 OpenJDK,使用 hsdis 作為反匯編器。下載合適版本的反匯編程序庫,在本例中是 hsdis-amd64.so,并將其放在 Java_HOME/lib/server 目錄下。使用時還需要在 -XX:+PrintAssembly 之前增加 -XX:+UnlockDiagnosticVMOptions 選項。否則,JVM 會給你一個警告。ttn28資訊網——每日最新資訊28at.com

完整命令如下:ttn28資訊網——每日最新資訊28at.com

$ java -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler / -XX:-TieredCompilation -XX:CompileThreshold=5 -XX:+UnlockDiagnosticVMOptions / -XX:+PrintAssembly Demo[...]5 iteration        178   56        b       Demo::<init> (5 bytes)Compiled method (c2)    178   56                Demo::<init> (5 bytes) total in heap  [0x00007fd4d08dad10,0x00007fd4d08dafe0] = 720 relocation     [0x00007fd4d08dae88,0x00007fd4d08daea0] = 24[...] handler table  [0x00007fd4d08dafc8,0x00007fd4d08dafe0] = 24[...] dependencies   [0x00007fd4d08db3c0,0x00007fd4d08db3c8] = 8 handler table  [0x00007fd4d08db3c8,0x00007fd4d08db3f8] = 48----------------------------------------------------------------------Demo.square(I)I  [0x00007fd4d08db1c0, 0x00007fd4d08db2b8]  248 bytes[Entry Point][Constants]  # {method} {0x00007fd4b841f4b0} 'square' '(I)I' in 'Demo'  # this:       rsi:rsi   = 'Demo'  # parm0:      rdx     = int  #             [sp+0x20]  (sp of caller)[...][Stub Code]  0x00007fd4d08db280: movabs $0x0,%rbx          ;   {no_reloc}  0x00007fd4d08db28a: jmpq   0x00007fd4d08db28a  ;   {runtime_call}  0x00007fd4d08db28f: movabs $0x0,%rbx          ;   {static_stub}  0x00007fd4d08db299: jmpq   0x00007fd4d08db299  ;   {runtime_call}[Exception Handler]  0x00007fd4d08db29e: jmpq   0x00007fd4d08bb880  ;   {runtime_call ExceptionBlob}[Deopt Handler Code]  0x00007fd4d08db2a3: callq  0x00007fd4d08db2a8  0x00007fd4d08db2a8: subq   $0x5,(%rsp)  0x00007fd4d08db2ad: jmpq   0x00007fd4d08a01a0  ;   {runtime_call DeoptimizationBlob}  0x00007fd4d08db2b2: hlt      0x00007fd4d08db2b3: hlt      0x00007fd4d08db2b4: hlt      0x00007fd4d08db2b5: hlt      0x00007fd4d08db2b6: hlt      0x00007fd4d08db2b7: hlt    ImmutableOopMap{rbp=NarrowOop }pc offsets: 96ImmutableOopMap{}pc offsets: 112ImmutableOopMap{rbp=Oop }pc offsets: 148 Square(i) = 25Time taken= 2567698--------------------------------6 iterationSquare(i) = 36Time taken= 76752[...]--------------------------------10 iterationSquare(i) = 100Time taken= 52888

我只截取了輸出中與 Demo.java 相關的部分。ttn28資訊網——每日最新資訊28at.com

現在再來看看 AOT 編譯。它是在 JDK9 中引入的特性。AOT 是用于生成 .so 這樣的庫文件的靜態編譯器。用 AOT 可以將指定的類編譯成 .so 庫。這個庫可以直接執行,而不用解釋或 JIT 編譯。如果 JVM 沒有檢測到 AOT 編譯的代碼,它會進行常規的解釋和 JIT 編譯。ttn28資訊網——每日最新資訊28at.com

使用 AOT 編譯的命令如下:ttn28資訊網——每日最新資訊28at.com

$ jaotc --output=libDemo.so Demo.class

用下面的命令來查看共享庫的符號表:ttn28資訊網——每日最新資訊28at.com

$ nm libDemo.so

要使用生成的 .so 庫,使用 -XX:+UnlockExperimentalVMOptions 和 -XX:AOTLibraryttn28資訊網——每日最新資訊28at.com

$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libDemo.so Demo1 iterationSquare(i) = 1Time taken= 7831139--------------------------------2 iterationSquare(i) = 4Time taken= 36619[...]10 iterationSquare(i) = 100Time taken= 42085

從輸出上看,跟完全用解釋的情況沒有區別。為了確認 AOT 發揮了作用,使用 -XX:+PrintAOTttn28資訊網——每日最新資訊28at.com

$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo         28        1         loaded        ./libDemo.so  aot library         80        1         aot[ 1]   Demo.main([Ljava/lang/String;)V         80        2         aot[ 1]   Demo.square(I)I         80        3         aot[ 1]   Demo.<init>()V1 iterationSquare(i) = 1Time taken= 7252921--------------------------------2 iterationSquare(i) = 4Time taken= 57443[...]10 iterationSquare(i) = 100Time taken= 53586

要確認沒有發生 JIT 編譯,用如下命令:ttn28資訊網——每日最新資訊28at.com

$ java -XX:+UnlockExperimentalVMOptions -Xbatch -XX:+PrintCompilation / -XX:CompileCommandFile=hotspot_compiler -XX:-TieredCompilation / -XX:CompileThreshold=3 -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo         19        1         loaded        ./libDemo.so  aot library         77        1         aot[ 1]   Demo.square(I)I         77        2         aot[ 1]   Demo.main([Ljava/lang/String;)V         77        3         aot[ 1]   Demo.<init>()V         77        2         aot[ 1]   Demo.main([Ljava/lang/String;)V   made not entrant[...]4 iterationSquare(i) = 16Time taken= 43366[...]10 iterationSquare(i) = 100Time taken= 59554

需要特別注意的是,修改被 AOT 編譯了的源代碼后,一定要重新生成 .so 庫文件。否則,過時的的 AOT 編譯庫文件不會起作用。例如,修改 square() 方法,使其計算立方值:ttn28資訊網——每日最新資訊28at.com

//Demo.javapublic class Demo {    public int square(int i) throws Exception {        return(i*i*i);    }    public static void main(String[] args) throws Exception {        for (int i = 1; i <= 10; i++) {          System.out.println("" + Integer.valueOf(i)+" iteration");          long start = System.nanoTime();          int r= new Demo().square(i);          System.out.println("Square(i) = " + r);          long end = System.nanoTime();          System.out.println("Time taken= " + (end-start));          System.out.println("--------------------------------");        }    }}

重新編譯 Demo.javattn28資訊網——每日最新資訊28at.com

$ java Demo.java

但不重新生成 libDemo.so。使用下面命令運行 Demottn28資訊網——每日最新資訊28at.com

$ java -XX:+UnlockExperimentalVMOptions -Xbatch -XX:+PrintCompilation -XX:CompileCommandFile=hotspot_compiler -XX:-TieredCompilation -XX:CompileThreshold=3 -XX:AOTLibrary=./libDemo.so -XX:+PrintAOT Demo         20        1         loaded        ./libDemo.so  aot library         74        1         n           java.lang.invoke.MethodHandle::linkToStatic(LLLLLL)L (native)   (static)2 iterationsqrt(i) = 8Time taken= 43838--------------------------------3 iteration        137   56        b            Demo::<init> (5 bytes)        138   57        b            Demo::square (6 bytes)sqrt(i) = 27Time taken= 534649--------------------------------4 iterationsqrt(i) = 64Time taken= 51916[...]10 iterationsqrt(i) = 1000Time taken= 47132

可以看到,雖然舊版本的 libDemo.so 被加載了,但 JVM 檢測出它已經過時了。每次生成 .class 文件時,都會在類文件中添加一個指紋,并在 AOT 庫中保存該指紋。修改源代碼后類指紋與舊的 AOT 庫中的指紋不匹配了,所以沒有執行 AOT 編譯生成的原生機器碼。從輸出可以看出,現在實際上是 JIT 在起作用(注意 -XX:CompileThreshold 被設置為了 3)。ttn28資訊網——每日最新資訊28at.com

AOT 和 JIT 之間的權衡

如果你的目標是減少 JVM 的預熱時間,請使用 AOT,這可以減少運行時負擔。問題是 AOT 沒有足夠的數據來決定哪段代碼需要預編譯為原生代碼。相比之下,JIT 在運行時起作用,卻對預熱時間有一定的影響。然而,它將有足夠的分析數據來更高效地編譯和反編譯代碼。ttn28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-17247-0.htmlJVM 解釋和編譯指南

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 超越基礎:Flutter 中 onTap 事件的五條規則讓你脫穎而出

下一篇: 如何精確控制 asyncio 中并發運行的多個任務

標簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 庆阳市| 清涧县| 吕梁市| 南昌市| 宣汉县| 株洲市| 九龙县| 苍梧县| 绵竹市| 边坝县| 花垣县| 朝阳县| 张掖市| 和龙市| 武乡县| 灵山县| 浪卡子县| 和田市| 沭阳县| 平南县| 正安县| 崇左市| 全椒县| 隆德县| 武安市| 桦甸市| 石狮市| 岳西县| 宜春市| 台州市| 呼玛县| 扶余县| 麦盖提县| 高尔夫| 科尔| 垫江县| 漳平市| 田林县| 紫云| 鹿邑县| 页游|