天天看點

Java 代碼編譯的3種方式,其中JIT最重要!

作者:博文小火柴

通過 Javac 将程式源代碼進行編譯,轉換成 Java 位元組碼,JVM 通過模闆方式把位元組碼翻譯成對應的機器指令,逐條讀入,逐條解釋翻譯,執行速度必然比可執行的二進制位元組碼程式慢得多。

為了提高執行速度,引入了 JIT 技術。JIT是 JVM 的重要組成部分,JIT 通過分析程式代碼,找到熱點的執行代碼,把部分位元組碼編譯成機器碼儲存起來用于下次調用。對于較小的方法,會嘗試進行内聯展開。

應用程式在大部分情況下很少考慮 JIT 的優化,這是一個自動過程。不過對于性能要求極高的工具或關鍵服務類,還是可以考慮 JIT 對代碼優化的影響,有時候性能能提高數百倍。

一、Java 的編譯通常有如下方式:

1.前端編譯 Javac:将 Java 源碼編譯成位元組碼。

2.提前編譯 AOT:将 Java 源碼編譯成機器碼,優點是執行速度快,缺點是犧牲了平台無關性,有些優化需要在運作過程中分析确認,AOT 做不到。系統中不常用的代碼也編譯了。Java 9 後提供了 jaotc。

3.即時編譯 JIT(Just-In-Time):位元組碼在執行過程中,動态編譯成機器碼的過程。JIT通常會分析系統的熱點,對熱點代碼會再次嘗試更加激進的優化措施進而提高 Java 系統性能.。

這裡的前端編譯是指将 Java 源碼編譯成 Java 位元組碼的過程,JDK 提供 javac 指令将 Java源程式編譯成 java class,指令格式是 javac [options] [sourcefiles-or-classnames]。

比如,編譯 Hello.java:

${java_home}/bin/javac Hello.java           

前端編譯 Java 源程式不一定是檔案,比如,可以使用 javax.tools.JavaCompiler(JDK 6 開始支援)類,将 getSource 傳回的字元串源碼編譯成位元組碼并儲存到 class 檔案中:

//CompileString.java
public static void main(String[] args) throws IOException {
       JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 
       DiagnosticCollector<JavaFileObject> diagnostics =
                     new DiagnosticCollector<>();
       StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, null, null);
              JavaStringObject stringObject =
                          new JavaStringObject("Test.java", getSource());
 
              String classes = System.getProperty("user.dir")+"/compile/javac/target/classes";
              File classesFile = new File(classes);
              fileManager.setLocation(CLASS_OUTPUT,Arrays.asList(classesFile));
 
              JavaCompiler.CompilationTask task = compiler.getTask(null,
                             fileManager, diagnostics, null, null, Arrays.asList(stringObject));
              boolean success = task.call();
              System.out.println(success?"編譯成功":"編譯失敗");
              diagnostics.getDiagnostics().forEach(System.out::println);
}
public static String getSource() {
       return "public class Test {"
             + " }";
}           

JavaCompiler 用于編譯 Java 代碼,javac 指令也會調用 JavaCompiler。diagnostics 用于在編譯過程中保留調試、警告或者錯誤資訊。

StandardJavaFileManager 對象用于管理源碼和編譯後輸出的檔案。本例子中設定了 CLASS_OUTPUT 目錄。JavaStringObject 是自定義的一個對象,繼承了 SimpleJavaFileObject,用于代表 Java 源代碼,定義如下:

JavaStringObject 最重要的方法是實作了 getCharContent,提供 Java 源碼。在這個例子中,Java 代碼以字元的形式提供,而不是檔案。

為了編譯 JavaStringObject,需要建立 CompilationTask,并執行 call 方法,代碼如下:

public class JavaStringObject extends SimpleJavaFileObject {
       private final String source;
       protected JavaStringObject(String name, String source) {
              super(URI.create(name), Kind.SOURCE);
              this.source = source;
       }       
       @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors)
                      throws IOException {
               return source;
         }
}           

JavaStringObject 最重要的方法是實作了 getCharContent,提供 Java 源碼。在這個例子中,Java 代碼以字元的形式提供,而不是檔案。

為了編譯 JavaStringObject,需要建立 CompilationTask,并執行 call 方法,代碼如下:

JavaCompiler.CompilationTask task = compiler.getTask(null,fileManager,
diagnostics, options, null, Arrays.asList(stringObject));
        boolean success = task.call();
        System.out.println(success?"編譯成功":"編譯失敗");
        diagnostics.getDiagnostics().forEach(System.out::println);           

如果 task.call 傳回 true,則表示編譯成功,可以在/compile/javac/target/classes 下找到編譯好的 Test.class。

代碼的最後列印出編譯過程中的調試、告警或者錯誤資訊。

内容摘自《高性能Java系統權威指南》第七章

Java 代碼編譯的3種方式,其中JIT最重要!

李家智 著

本書特點:

内容上,總結作者從事Java開發20年來在頭部IT企業的高并發系統經曆的真實案例,極具參考意義和可讀性。

對于程式員和架構師而言,Java 系統的性能優化是一個超正常的挑戰。這是因為 Java 語言和 Java 運作平台,以及 Java 生态的複雜性決定了 Java 系統的性能優化不再是簡單的更新配置或者簡單的 “空間換時間”的技術實作,這涉及 Java 的各種知識點。

本書從高性能、易維護、代碼增強以及在微服務系統中編寫Java代碼的角度來描述如何實作高性能Java系統,結合真實案例,讓讀者能夠快速上手實戰。

風格上,本書的風格偏實戰,讀者可以下載下傳書中的示例代碼并運作測試。讀者可以從任意一章開始閱讀,掌握性能優化知識為公司的系統所用。

本書适合:

中進階程式員和架構師;

以及有志從事基礎技術研發、開源工具研發的極客閱讀;

也可以作為 Java 筆試和面試的參考書。

繼續閱讀