深刻分析 Javac 編譯原理

Javac是什麼

一般,一個java文件會經過編譯器編譯成字節碼文件.class,再又java虛擬機JVM翻譯成計算機可執行的文件。java

咱們所知道的java語言有它本身的語法規範,一樣的JVM也有它的語法規範,如何讓java的語法規則去適應語法解析規則,這就是javac的做用,簡而言之,javac的做用就是將java源代碼轉化成class字節碼文件。bash

Javac編譯器的基本結構

編譯步驟

image

1. 詞法分析器:

1.1做用:

將源碼轉化爲Token流jvm

1.2流程

讀取源代碼,從源文件的一個字符開始,按照java語法規範依次找出package,import,類定義,屬性,方法定義等,最後構建出一個抽象語法樹函數

1.3舉例

package compile;

/**
 * 詞法解析器
 */
 public class Cifa{
     int a;
     int c = a + 1;
 }
複製代碼

轉化爲Token流: 源碼分析

image

1.4源碼分析

  • com.sun.tools.javac.parser.JavacParser  規定哪些詞符合Java語言規範,具體讀取和歸類不一樣詞法的操做由scanner完成
  • com.sun.tools.javac.parser.Scanner  負責逐個讀取源代碼的單個字符,而後解析符合Java語言規範的Token序列,調用一次nextToken()都構造一個Token
  • com.sun.tools.javac.parser.Tokens$TokenKind  裏面包含了全部token的類型,譬如BOOLEAN,BREAK,BYTE,CASE。
  • com.sun.tools.javac.util.Names  用來存儲和表示解析後的詞法,每一個字符集合都會是一個Name對象,全部的對象都存儲在Name.Table這個內部類中。
  • com.sun.tools.javac.parser.KeyWords  負責將字符集合對應到token集合中,如,package zxy.demo.com; Token.PACKAGE = package, Token.IDENTIFIER = zxy.demo.com,(這部分又分爲讀取第一個token,爲zxy,判斷下一個token是否爲「.」,是的話接着讀取下一個Token.IDENTIFIER類型的token,反覆直至下一個token不是」.」,也就是說下一個不是Token.IDENIFIER類型的token,Token.SEMI = ;即這個TIDENTIFIER類型的token的Name讀完),KeyWords類負責此任務。

1.5問題

Javac是如何分辨這一個個Token呢?例如它時如何直到package是關鍵詞而不是自定義變量呢?

Javac在進行此法分析時會由JavacParser根據Java語言規範來控制什麼順序,地方會出現什麼Token,例如package就只能在文件的最開頭出現ui

Javac怎樣肯定哪些字符組合在一塊兒就是一個Token呢?它如何從一串字符流中劃分出Token來?

對於關鍵字,主要由關鍵字的語法規則,例如package就是若一個字符串package是連續的,那麼他就是關鍵字this

對於自定義變量名稱,自定義名稱之間用空格隔開,每一個語法表達式用分號結束spa

舉例:

int a = 1 + 2;翻譯

從package開始code

.....

int 就是經過語法關鍵字斷定的TOKEN:INT

int a之間經過空格隔開

a 就是自定義的變量被斷定爲TOKEN:IDENTIFIER

a =之間經過空格隔開(這時有的小夥伴就會說了,int a=b+c;這句話也不報錯啊,對的,大多數時候,這種不用空格分開確實可以編譯,這是由於java指出聲明變量的時候必須以字母、下劃線或者美圓符開頭,當JavacParser讀完a去讀=的時候就直到這個=不屬於變量了)將=斷定爲TOKEN:EQ

1被斷定爲TOKEN:INTLITERAL

.....

將;識別爲TOKEN:SEMI

.....

最後讀取到類結束,也就是}被斷定爲TOKEN:RBRACE

2.語法分析器:

剛纔,詞法解析器已經將Java源文件解析成了Token流。

如今,語法解析器就要將Token流組建成更加結構化的語法樹。也就是將這些Token流中的單詞裝成一句話,完整的語句。

2.1做用

將進行詞法分析後造成的Token流中的一個個Token組成一句句話,檢查這一句句話是否是符合Java語言規範。

2.2語法分析三部分

  • package
  • import
  • 類(包含class、interface、enum),一下提到的類泛指這三類,並不僅僅是指class

2.3所用類庫

  • com.sun.tools.javac.tree.TreeMaker  全部語法節點都是由它生成的,根據Name對象構建一個語法節點
  • com.sun.tools.javac.tree.JCTree$JCIf   全部的節點都會繼承jctree和實現**tree,譬如 JCIf extends JCTree.JCStatement implements IfTree
  • com.sun.tools.javac.tree.JCTree的三個屬性
    • Tree tag:每一個語法節點都會以整數的形式表示,下一個節點在上一個節點上加1;
      複製代碼
    • pos:也是一個整數,它存儲的是這個語法節點在源代碼中的起始位置,一個文件的位置是0,而-1表示不存在
      複製代碼
    • type:它表明的是這個節點是什麼java類型,如int,float,仍是string等
      複製代碼

2.4 舉例

package compile;

/**
 * 語法
 */
public class Yufa {
    int a;
    private int c = a + 1;
    
    //getter
    public int getC() {
        return c;
    }
    //setter
    public void setC(int c) {
        this.c = c;
    }
}
複製代碼

image

  • 每個包package下的全部類都會放在一個JCCompilationUnit節點下,在該節點下包含:package語法樹(做爲pid)、各個類的語法樹
  • 每個從JCClassDecl發出的分支都是一個完整的代碼塊,上述是四個分支,對應咱們代碼中的兩行屬性操做語句和兩個方法塊代碼塊,這樣其實就完成了語法分析器的做用:將一個個Token單詞組成了一句句話(或者說成一句句代碼塊)
  • 在上述的語法樹部分,對於屬性操做部分是完整的,可是對於兩個方法塊,省略了一些語法節點,例如:方法修飾符public、方法返回類型、方法參數。

注1:若類中有import關鍵字則途中還有import的語法節點

注2:全部語法節點的生成都是在TreeMaker類中完成的

3.語義分析器

3.1做用

將語法樹轉化爲註解語法樹,即在這顆語法樹上作一些處理

3.2步驟

  • 給類添加默認構造函數(由com.sun.tools.javac.comp.Enter類完成)

  • 處理註解(由com.sun.tools.javac.processing.JavacProcessingEnvironment類完成)

  • 檢查語義的合法性並進行邏輯判斷(由com.sun.tools.javac.comp.Attr完成)

    • 變量的類型是否匹配
    • 變量在使用前是否初始化
    • 可以推導出泛型方法的參數類型
    • 字符串常量合併
  • 數據流分析(由com.sun.tools.javac.comp.Flow類完成)

    • 檢驗變量是否被正確賦值(eg.有返回值的方法必須肯定有返回值)
    • 保證final變量不會被重複修飾
    • 肯定方法的返回值類型
    • 全部的檢查型異常是否拋出或捕獲
    • 全部的語句都要被執行到(return後邊的語句就不會被執行到,除了finally塊兒)
  • 對語法樹進行語義分析(由com.sun.tools.javac.comp.Flow執行)

    • 去掉無用的代碼,如只有永假的if代碼塊
    • 變量的自動轉換,如將int自動包裝爲Integer類型
    • 去除語法糖,將foreach的形式轉化爲更簡單的for循環

最終,生成了註解語法樹

3.3所用類庫

  • com.sun.tools.javac.comp.Check,它用來輔助Attr類檢查語法樹中變量類型是否正確,如方法返回值是否和接收的引用值類型匹配
  • com.sun.tools.javac.comp.Resolve,用來檢查變量,方法或者類的訪問是否合法,變量是不是靜態變量
  • com.sun.tools.javac.comp.ConstFold,將一個字符串常量中的多個字符合併成一個字符串
  • com.sun.tools.javac.comp.Infer,幫助推導泛型方法的參數類型

3.4舉例

變量自動轉化

public class Yuyi{
    public static void main(String agrs[]){
        Integer i = 1;
        Long l = i + 2L;
        System.out.println(l);
    }
}
//通過自動轉換後
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        Integer i = Integer.valueOf(1);
        Long l = Long.valueOf(i.intValue() + 2L);
        System.out.println(l);
    }
}
複製代碼

解除語法糖

public class Yuyi{
    public static void main(String agrs[]){
        int[] array = {1,2,3};
        for (int i : array){
            System.out.println(i);
        }
    }
}
//解除語法糖後
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        int[] arrays = {1,2,3};
        for (int[] arr$ = array,len$=arr$.length,i$=0; i$<len$; ++i$){
            int i = arr$[i$];
            {
                System.out.println(i);
            }
        }
    }
}
複製代碼

內部類解析

public class Yuyi{
    public static void main(String agrs[]){
        Inner inner = new Inner();
        inner.print();
    }
    class Inner{
        public void print(){
            System.out.println("Yuyi$Inner.print");
        }
    }
}
//轉化後的代碼以下
public class Yuyi{
    public Yuyi(){
        super();
    }
    public static void main(String agrs[]){
        Yuyi$Inner inner = new Yuyi$Inner(this);
        inner.print();
    }
    {
    }
}
class Yuyi$Inner{
    /*synthetic*/ final Yuyi this$0;
    
    Yuyi$Inner(/*synthetic*/final Yuyi this$0){
        this.this$0 = this$0;
        super();
    }
    
    public void print(){
        System.out.println("Yuyi$Inner.print");
    }
}

複製代碼

4.代碼生成器

4.1做用

生成語法樹後,接下來Javac會調用com.sun.tools.javac.jvm.Gen類遍歷語法樹,生成Java字節碼

4.2步驟

  • 將java方法中代碼塊轉化爲符合JVM語法的命令形式,JVM的操做都是基於棧的,全部的操做都必須通過出棧和進棧來完成
  • 按照JVM的文件組織格式將字節碼輸出到以class爲拓展名的文件中

4.3所用類庫

  • com.sun.tools.javac.jvm.Gen類,用來遍歷語法樹,生成最終Java字節碼
  • com.sun.tools.javac.jvm.Items,輔助gen,這個類表示任何可尋址的操做項,這些操做項均可以做爲一個單位出如今操做棧上
  • com.sun.tools.javac.jvm.Code,輔助gen,存儲生成的字節碼,並提供一些可以映射操做碼的方法

參考書籍:《深刻分析Java Web》

相關文章
相關標籤/搜索