JVM筆記-前端編譯與優化

      網友投稿 534 2022-05-29

      1. 概述

      所謂”編譯“,通俗來講就是把我們寫的代碼“翻譯“成機器可以讀懂的機器碼。而編譯器就是做這個翻譯工作的。

      Java 技術中的編譯器可以分為如下三類:

      前端編譯器:把 .java 文件轉變為 .class 文件的過程。比如 JDK 的 Javac。

      即時編譯器:Just In Time Compiler,常稱 JIT 編譯器,在「運行期」把字節碼轉變為本地機器碼的過程。比如 HotSpot VM 的 C1、C2 編譯器,Graal 編譯器。

      提前編譯器:Ahead Of Time Compiler,常稱 AOT 編譯器,直接把程序編譯成與目標機器指令集相關的二進制代碼的過程。比如 JDK 的 Jaotc,GNU Compiler for the Java。

      其中后面兩類都屬于后端編譯器。

      本文主要分析前端編譯器 Javac 的相關內容,后文再介紹后端編譯器。

      2. Javac 編譯器

      Javac 的編譯過程大致可以分為 1 個準備過程和 3 個處理過程:

      準備過程:初始化插入式注解處理器

      解析與填充符號表過程

      詞法、語法分析:將源碼中的字符流轉變為標記集合,構造抽象語法樹

      填充符號表:產生符號地址和符號信息

      插入式注解處理器的注解處理過程

      分析與字節碼生成過程

      標注檢查:對語法的靜態信息進行檢查

      數據流及控制流分析:對程序的動態運行過程進行檢查

      解語法糖:將簡化代碼編寫的語法糖還原為原來的樣子

      字節碼生成:將前面各個步驟所生成的信息轉化為字節碼

      2.1 解析與填充符號表

      2.1.1 詞法、語法分析

      詞法分析

      將源碼中的字符流轉變為標記(Token)集合的過程。關鍵字、變量名、運算符等都可作為標記。比如下面一行代碼:

      int a = b + 2;

      在字符流中,關鍵字 int 由三個字符組成,但它是一個獨立的標記,不可再分。

      該過程有點類似“分詞”的過程。雖然這些代碼我們一眼就能認出來,但編譯器要逐個分析過之后才能知道。

      語法分析

      根據上面的標記序列構造抽象語法樹的過程。

      抽象語法樹(Abstract Syntax Tree,AST)是一種用來描述程序代碼語法結構的樹形表示方法,每個節點都代表程序代碼中的一個語法結構(Syntax Construct),比如包、類型、修飾符等。

      通俗來講,詞法分析就是對源碼文件做分詞,語法分析就是檢查源碼文件是否符合 Java 語法。

      2.1.2 填充符號表

      符號表(Symbol Table)是一種數據結構,它由一組符號地址和符號信息組成(類似“鍵-值”對的形式)。

      符號由抽象類 com.sun.tools.javac.code.Symbol 表示,Symbol 類有多種擴展類型的符號,比如 ClassSymbol 表示類、MethodSymbol 表示方法等。

      符號表記錄的信息在編譯的不同階段都要用到,如:

      用于語義檢查和產生中間代碼;

      在目標代碼生成階段,符號表是對符號名進行地址分配的依據。

      這個階段主要是根據上一步生成的抽象語法樹列表完成符號填充,返回填充了類中所有符號的抽象語法樹列表。

      2.2 注解處理器

      JDK 5 提供了注解(Annotations)支持,JDK 6 提供了“插入式注解處理器”,可以在「編譯期」對代碼中的特定注解進行處理,從而影響前端編譯器的工作過程。

      比如效率工具 Lombok 就是在這個階段進行處理的。示例代碼:

      import lombok.Getter; @Getter public class Person { private String name; private Integer age; }

      該代碼編譯后:

      public class Person { private String name; private Integer age; public Person() { } public String getName() { return this.name; } public Integer getAge() { return this.age; } }

      其中兩個 getter 方法就是 @Getter 注解在這個階段產生的效果(具體實現原理網上可以找到相關內容)。

      2.3 語義分析與字節碼生成

      抽象語法樹能表示一個結構正確的源程序,卻無法保證語義是否符合邏輯。

      而語義分析就對語法正確的源程序結合上下文進行相關性質的檢查(類型檢查、控制流檢查等)。比如:

      int a = 1; boolean b = false; // 這樣賦值顯然是錯誤的 // 但在語法上是沒問題的,這個錯誤是在語義分析時檢查的 int c = a + b;

      Javac 在編譯過程中,語義分析過程可分為標注檢查和數據及控制流分析兩個步驟。

      2.3.1 標注檢查

      檢查內容:變量使用前是否已被聲明、變量與賦值之間的數據類型是否匹配等。

      常量折疊

      該過程中,還會進行一個常量折疊(Constant Folding)的代碼優化。

      比如,我們在代碼中定義如下:

      int a = 1 + 2;

      在抽象語法樹上仍能看到字面量 "1"、"2" 和操作符 "+",但經過常量折疊優化后,在語法樹上將會被標注為 "3"。

      2.3.2 數據及控制流分析

      主要檢查內容:

      局部變量使用前是否賦值

      方法的每條路徑是否有返回值

      受檢查異常是否被正確處理等

      2.3.3 解語法糖

      語法糖(Syntactic Sugar):也稱糖衣語法,指的是在計算機語言中添加某種語法,該語法對語言的編譯結果和功能并沒有實際影響,卻能更方便程序猿使用該語言。

      PS: 就是讓我們寫代碼更舒服的語法,像吃了糖一樣甜。

      Java 中常見的語法糖有泛型、變長參數、自動裝箱拆箱等。

      JVM 其實并不支持這些語法,它們在編譯階段要被還原成原始的基礎語法結構。該過程就稱為解語法糖(打回原形)。

      2.3.4 字節碼生成

      Javac 編譯過程的最后一個階段。主要是把前面各個步驟生成的信息轉換為字節碼指令寫入磁盤中。

      此外,編譯器還進行了少量的代碼添加和轉換工作。比如實例構造器

      () 和類構造器

      () 方法就是在這個階段被添加到語法樹的。

      還有一些代碼替換工作,例如將字符串的 "+" 操作替換為 StringBuilder(JDK 5 及以后)或 StringBuffer(JDK 5 之前) 的 append() 操作。

      3. Java 語法糖

      3.1 泛型

      JVM筆記-前端編譯與優化

      泛型這個概念大家應該都不陌生,Java 是從 5.0 開始支持泛型的。

      由于歷史原因,Java 使用的是“類型擦除式泛型(Type Erasure Generics)”,也就是泛型只會在源碼中存在,編譯后的字節碼文件中,全部泛型會被替換為原先的裸類型(Raw Type)。

      因此,在運行期間 List

      和 List

      其實是同一個類型。例如:

      public class GenericTest { public static void main(String[] args) { List

      l1 = new ArrayList<>(); l1.add(1); List

      l2 = new ArrayList<>(); l2.add("2"); } }

      經編譯器擦除類型后:

      public class GenericTest { public GenericTest() { } public static void main(String[] var0) { // 原先的泛型都沒了 ArrayList var1 = new ArrayList(); var1.add(1); ArrayList var2 = new ArrayList(); var2.add("2"); } }

      類型擦除是有缺點的,比如:

      由于類型擦除,會將泛型的類型轉為 Object,但是 int、long 等原始數據類型無法與 Object 互轉,這就導致了泛型不能支持原始數據類型。進而引起了使用包裝類(Integer、Long 等)帶來的拆箱、裝箱問題。

      運行期無法獲取泛型信息。

      3.2 自動裝箱、拆箱與遍歷

      遍歷代碼示例

      public class GenericTest { public static void main(String[] args) { List

      list = Arrays.asList("hello", "world"); for (String s : list) { System.out.println(s); } } }

      反編譯版本 1:

      public class GenericTest { public GenericTest() { } public static void main(String[] args) { List

      list = Arrays.asList("hello", "world"); // 使用了迭代器 Iterator 遍歷 Iterator var2 = list.iterator(); while(var2.hasNext()) { String s = (String)var2.next(); System.out.println(s); } } }

      反編譯版本 2:

      public class GenericTest { public static void main(String[] args) { // 創建一個數組 List

      list = Arrays.asList(new String[] { "hello", "world" }); for (String s : list) System.out.println(s); } }

      不同的反編譯器得出的結果可能有所不同,這里找了兩個版本對比分析。

      從上面兩個版本的反編譯結果可以看出:Arrays.asList() 方法其實創建了一個數組,而增強 for 循環實際調用了迭代器 Iterator。

      自動拆裝箱代碼示例

      public class GenericTest { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); System.out.println(e == f); System.out.println(c == (a + b)); System.out.println(c.equals(a + b)); System.out.println(g == (a + b)); System.out.println(g.equals(a + b)); } }

      類似代碼估計大家都見過,畢竟有些面試題就喜歡這么搞,這些語句的輸出結果是什么呢?

      我們先看反編譯后的代碼:

      public class GenericTest { public static void main(String[] args) { Integer a = Integer.valueOf(1); Integer b = Integer.valueOf(2); Integer c = Integer.valueOf(3); Integer d = Integer.valueOf(3); Integer e = Integer.valueOf(321); Integer f = Integer.valueOf(321); Long g = Long.valueOf(3L); System.out.println((c == d)); // t System.out.println((e == f)); // f System.out.println((c.intValue() == a.intValue() + b.intValue())); // t System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue()))); // t System.out.println((g.longValue() == (a.intValue() + b.intValue()))); // t System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue()))); // f } }

      可以看到,編譯器對上述代碼做了自動拆裝箱的操作。其中值得注意的是:

      包裝類的 "==" 運算不遇到算術運算時,不會自動拆箱。

      equals() 方法不會處理數據轉型。

      此外,還有個值得玩味的地方:為何 c==d 是 true、而 e==f 是 false 呢?似乎也是個考點。

      這就要查看 Integer 類的 valueOf() 方法的源碼了:

      static final int low = -128; public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }

      可以看到 Integer 內部使用了緩存 IntegerCache:其最小值為 -128,最大值默認是 127。因此,[-128, 127] 范圍內的數字都會直接從緩存獲取。

      而且,該緩存的最大值是可以修改的,可以使用如下 VM 參數將其修改為 500:

      -XX:AutoBoxCacheMax=500

      增加該參數后,上述 e==f 也是 true 了。

      Java IDE web前端 JVM

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:位置傳感器:電位器,電容位置傳感器
      下一篇:番外2. OpenCV 中攝像頭捕獲與視頻處理與常見問題解決方案
      相關文章
      国产亚洲av片在线观看播放| 亚洲色自偷自拍另类小说| 亚洲欧洲日韩不卡| 亚洲午夜久久久久妓女影院| 亚洲一区二区三区免费| 亚洲国产成人影院播放| 国产亚洲综合视频| 亚洲第一区精品日韩在线播放| 蜜芽亚洲av无码一区二区三区| 亚洲乱码中文字幕在线| 亚洲精品一卡2卡3卡四卡乱码| 亚洲中文字幕无码中文| 亚洲熟妇久久精品| WWW亚洲色大成网络.COM| 国产精品亚洲综合天堂夜夜| 日日摸日日碰夜夜爽亚洲| 一本色道久久88亚洲综合| 亚洲成av人片在线观看天堂无码| 国产AV日韩A∨亚洲AV电影 | 亚洲欧洲精品国产区| 亚洲欧洲春色校园另类小说| 亚洲成年人电影在线观看| 亚洲乱人伦精品图片| 亚洲影院天堂中文av色| 亚洲精华国产精华精华液网站| 亚洲AV成人精品日韩一区| 大胆亚洲人体视频| 亚洲熟妇av一区二区三区| 亚洲av福利无码无一区二区| 亚洲久本草在线中文字幕| 亚洲欧洲日本精品| 99久久国产亚洲综合精品| 亚洲大码熟女在线观看| 亚洲VA综合VA国产产VA中| 亚洲熟妇中文字幕五十中出| 亚洲va在线va天堂va四虎| 亚洲神级电影国语版| 亚洲一线产品二线产品| 久久久久久久久无码精品亚洲日韩| 亚洲成?Ⅴ人在线观看无码| 国产亚洲成人久久|