《深入理解Java虛擬機(jī)-JVM高級(jí)特性與最佳實(shí)踐》學(xué)習(xí)總結(jié)(第十章)

第十章 早期(編譯期)優(yōu)化
早期(編譯期)優(yōu)化
目錄:

10.1 對(duì)于編譯器的概述
10.2 Javac編譯器
10.3 媽媽,我想吃語(yǔ)法糖!
10.4 實(shí)戰(zhàn):插入式注解處理器

10.1 對(duì)于編譯器的概述

Java語(yǔ)言的"編譯期"是一段"不確定"的操作過程,因?yàn)樗赡苁侵赶蛞粋€(gè)前端編譯器(編譯器前端)把.java文件轉(zhuǎn)變成.class文件的過程;也可能是指虛擬機(jī)的后端運(yùn)行期編譯器(例如JIT編譯器,Just In Time Compiler)把字節(jié)碼轉(zhuǎn)變成機(jī)器碼的過程;還可以能是指使用靜態(tài)提前編譯器(例如AOT編譯器,Ahead Of Time Compiler)直接把*.java文件編譯成本地機(jī)器代碼的過程。
列舉三類比較有代表性的編譯器:

  1. 前端編譯器:Sun的javac、EclipseJDT中的增量式編譯器(ECJ)
  2. JIT編譯器:HotSpot VM的C1、C2編譯器
  3. AOT編譯器:GNU Compiler for the Java (GCJ)、Excelsior JET
10.2 Javac編譯器

10.2.1實(shí)現(xiàn)類:
com.sun.tools.javac.main.JavaCompiler的compile()和compile2()方法里。
10.2.2實(shí)現(xiàn)過程:

  1. 解析與填充符號(hào)表過程(Parse and Enter)
    詞法、語(yǔ)法分析
    詞法分析是將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記(Token)集合,單個(gè)字符是程序編寫過程的最小元素,而標(biāo)記則是編譯過程的最小元素,關(guān)鍵字、變量名、字面量和運(yùn)算符都可以成為標(biāo)記。
    語(yǔ)法分析是根據(jù)Token序列來構(gòu)造抽象語(yǔ)法樹的過程,它是一種用來描述程序代碼語(yǔ)法結(jié)構(gòu)的樹形表示方式,語(yǔ)法樹的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu),例如包、類型、修飾符、運(yùn)算符、接口、返回值甚至連代碼注釋等都可以是一個(gè)語(yǔ)法結(jié)構(gòu)。該過程由類com.sun.tools.javac.parser.Parser來完成,產(chǎn)出的抽象語(yǔ)法樹由com.sun.tools.javac.tree.JCTree類來表示
    填充符號(hào)表的過程由com.sun.tools.javac.comp.Enter類實(shí)現(xiàn)。

  2. 插入式注解處理器的注解處理過程(Annotation Processing)
    JDK1.5之后,Java語(yǔ)言提供了對(duì)注解(Annotations)的支持,這些注解與普通的Java代碼一樣,是在運(yùn)行期間發(fā)揮作用的。在JDK1.6中實(shí)現(xiàn)了JSR-269規(guī)范,提供了一組插入式注解處理器的標(biāo)準(zhǔn)API在編譯期間對(duì)注解進(jìn)行處理,我們可以把它看作是一組編譯器的插件,在這些插件里面,可以讀取、修改、添加抽象語(yǔ)法樹中的任意元素。

  3. 分析與字節(jié)碼生成過程(Analyse and Generate)
    語(yǔ)義分析 = 標(biāo)注檢查 + 數(shù)據(jù)及控制流分析
    語(yǔ)法樹能表示一個(gè)結(jié)構(gòu)正確的源程序的抽象,但無法保證源程序是符合邏輯的,而語(yǔ)義分析的主要任務(wù)是結(jié)構(gòu)上正確的源程序進(jìn)行上下文有關(guān)性質(zhì)的審查。
    1.標(biāo)注檢查
    標(biāo)注檢查步驟檢查的內(nèi)容包括諸如變量使用前是否已被聲明、變量和賦值之間的數(shù)據(jù)類型是否能夠匹配,并且還有一個(gè)重要?jiǎng)幼鹘凶?常量折疊",例如
    int a = 1 + 2
    在語(yǔ)法樹上可以看到字面量"1","2"和操作符"+"號(hào),經(jīng)過常量折疊之后,它們將會(huì)被折疊為字面量"3"
    實(shí)現(xiàn)類為com.sun.tools.javac.comp.Attr和com.sun.tools.javac.comp.Check
    2.數(shù)據(jù)及控制流分析
    其作用是對(duì)程序上下文邏輯更進(jìn)一步的驗(yàn)證,它可以檢查出諸如程序局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理了等問題。
    3.解語(yǔ)法糖(Syntactic Sugar)
    指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒有影響,但是更方便程序員使用。通常來說使用語(yǔ)法糖能夠增加程序的可讀性,從而減少程序代碼出錯(cuò)的機(jī)會(huì)。
    解語(yǔ)法糖的過程由desugar()方法觸發(fā),在com.sun.tools.javac.comp.TransTypes類和com.sun.tools.javac.comp.Lower類中完成
    4.字節(jié)碼生成
    字節(jié)碼生成是Javac編譯過程的最后一個(gè)階段,由類com.sun.tools.javac.jvm.Gen完成,字節(jié)碼生成階段不僅僅是把前面各個(gè)步驟所生成的信息(語(yǔ)法樹、符號(hào)表)轉(zhuǎn)化成字節(jié)碼寫到磁盤中,編譯器還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作。

10.3 Java語(yǔ)法糖

10.3.1 適用場(chǎng)景:

  1. 泛型:Java中的泛型表達(dá)只在源碼中存在,在編譯后的字節(jié)碼文件中,就已經(jīng)被替換為原來的原生類型了,并在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此對(duì)于運(yùn)行期的Java語(yǔ)言來說,ArrayList<int>與ArrayList<String>就是同一個(gè)類。這種實(shí)現(xiàn)方法成為"類型擦除"。
    2.自動(dòng)裝箱和拆箱
    調(diào)用Integer.valueOf()和Integer.intValue()
    注意:
Integer a = new Integer(100);
Integer b = new Integer(100);
System.out.println(a == b) -> true

因?yàn)?128 < 100 > 127 ,可以理解成緩存中有打包過相同的值

Integer c = new Integer(200);
Integer d = new Integer(200);
System.out.println(c == d) -> false

200> 127 ,這個(gè)時(shí)候直接就創(chuàng)建了兩個(gè)不同的Integer實(shí)例。
3.遍歷循環(huán)
將foreach的遍歷循環(huán)轉(zhuǎn)換成了迭代器的實(shí)現(xiàn),這也是為何遍歷循環(huán)需要被遍歷的類實(shí)現(xiàn)Iterable接口的原因。
4.變長(zhǎng)參數(shù)
在調(diào)用時(shí)候變成了一個(gè)數(shù)組類型的參數(shù)
** 有關(guān)這幾顆語(yǔ)法糖果,有代碼為證 **
Java源碼

package JVMLearning.tenChapter;

import java.util.Arrays;
import java.util.List;

/**
 * Created by Max on 2016/9/17.
 */
public class SyntacticSugarTest {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4);
        int sum = 0;
        for (int i : list) {
            sum += i;
        }
        System.out.println(sum);
    }
}

經(jīng)過反編譯之后的源碼:

package JVMLearning.tenChapter;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class SyntacticSugarTest
{
  public static void main(String[] args)
  {
    List<Integer> list = Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
    int sum = 0;
    for (Iterator localIterator = list.iterator(); localIterator.hasNext();)
    {
      int i = ((Integer)localIterator.next()).intValue();
      sum += i;
    }
    System.out.println(sum);
  }
}

將源碼與反編譯之后的源碼對(duì)比可以看出,在進(jìn)行初始化對(duì)泛型為Integer的List初始化的時(shí)候,調(diào)用了Integer.valueOf()方法,將基本類型int轉(zhuǎn)換成了Integer,并且用數(shù)組來解決變長(zhǎng)參數(shù)問題,在進(jìn)行遍歷循環(huán)的時(shí)候,將foreach語(yǔ)句變成了調(diào)用List的迭代器進(jìn)行遍歷。

5.條件編譯
Java語(yǔ)言之中并沒有使用預(yù)處理器,因?yàn)镴ava語(yǔ)言天然的編譯方式(編譯器并非一個(gè)一個(gè)編譯java文件,而是將所有的編譯單元的語(yǔ)法樹頂級(jí)節(jié)點(diǎn)輸入到待處理列表后再進(jìn)行編譯,因此各個(gè)文件之間能夠互相提供符號(hào)信息)無序使用預(yù)處理器。
Java實(shí)現(xiàn)條件編譯可以使用條件為變量的if語(yǔ)句
例如:

public static void main(String[] args) {
    if (true) {
        System.out.println("I'm first to go");
    }
    else {
        System.out.println("oh,I'd like to know if i can go");
    }
}
10.4實(shí)戰(zhàn):插入式注解處理器

根據(jù)插入式注解處理器提供的API來做一個(gè)簡(jiǎn)易的編譯器,功能就是檢測(cè)類、方法和字段的命名是否合乎規(guī)范,如果不合乎,就不提示W(wǎng)ARNING信息
注解處理器需要繼承javax.annotation.processing.AbstractProcessor,實(shí)現(xiàn)process()方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容