天天看點

Javac編譯過程

歡迎支援筆者新作:《深入了解Kafka:核心設計與實踐原理》和《RabbitMQ實戰指南》,同時歡迎關注筆者的微信公衆号:朱小厮的部落格。

Javac編譯過程
  Javac編譯過程大緻分為4個過程,分别是:

  1. 詞法分析
  2. 文法分析
  3. 語義分析
  4. 代碼生成

##詞法分析

  詞法分析是将源代碼的字元流轉變為标記(Token)集合,單個字元是程式編寫過程的最小元素,而标記則是編譯過程的最小元素,關鍵字、變量名、字面量、運算符都可以成為編輯,如“int a+b=2”這句代碼中包含了6個标記,分别是int、a、=、b、+、2,雖然關鍵字int由三個字元構成,但是它隻是一個Token,不可再拆分。在Javac的源碼中,詞法分析過程由com.sun.tools.javac.parser.Scanner類來實作。

##文法分析

  詞法分析器的作用是将Java源檔案的字元流轉變成對應的Token流。而文法分析器是将詞法分析器分的Token流元件成更加結構化的文法樹,也就是将一個個單詞組裝成一句話,一個完整的語句。哪些詞語組合在一起是主語,哪些是謂語、哪些是賓語、哪些是定語等沒要做進一步區分。

  文法分析是根據Token序列構造抽象文法樹的過程,抽象文法樹是一種用來描述程式代碼文法結構的樹形表示方式,文法樹的每一個節點都代表着程式代碼中的一個文法結構,例如包、類型、修飾符、運算符、接口、傳回值甚至代碼注釋等都可以是一個文法結構。文法分析過程由com.sun.tools.javac.parser.Parser類實作,這個階段産出的抽象文法樹由com.sun.tools.javc.tree.JCTree類表示,經過這個步驟之後,編譯器就基本不會再對源碼檔案進行操作了,後續的操作都是建立在抽象文法樹上。

##語義分析

  文法分析之後,編譯器獲得了程式代碼的抽象文法樹表示,文法樹能表示一個結構正确的源程式的抽象,但無法保證源程式是符合邏輯的。語義分析是要在文法樹的基礎上再做一些處理,如給類添加預設的構造函數,檢查變量在使用前是否已經初始化,将一些常量進行合并處理,檢查操作變量類型是否比對,檢查所有的操作語句是否可達,檢查checked exception是否正确處理。

  語義分析階段分為:填充符号表、标注檢查、資料及控制流分析。

填充符号表

  符号表是由一組符号位址和符号資訊構成的表格,讀者可以把它想象成哈希表K-V值對的形式。符号表中所登記的資訊在編譯的不同階段都要用到。在語義分析中,符号表所登記的内容将用于語義檢測和産生中間代碼。在目标代碼生成階段,當對符号名進行位址配置設定時,符号表是位址配置設定的依據。在Javac源碼中,填充符号表的過程由com.sun.tools.javac.comp.Enter類實作。

  一個類除了類本身會定義一些符号變量如類名稱、變量名稱和方法名稱等,還有一些符号是引用其它類的,這些符号會調用其它類的方法或者變量等,還有一些類可能會繼承或者實作超類和接口等。這些符号都是在其他類中定義的,那麼就需要将這些類的符号也解析到符号表中。

  在Enter類解析這一步驟中,還有一個重要的步驟就是添加預設的構造函數。如果代碼中沒有提供任何構造函數,那麼編譯器将會添加一個沒有參數、通路下與目前一緻的預設構造函數。

标注檢查

  檢查的内容包括諸如變量的類型是否比對、變量在使用前是否已經初始化、能夠推導出泛型方法的參數類型、字元串常量的合并(常量折疊)。在标注檢查步驟中一個重要的動作稱為常量折疊,如果我們在代碼中寫了如下定義:

int a=1+2;
           

  那麼在文法樹上仍然能看到字面量1、2以及操作符+,但是在進過常量折疊之後,他們将會被折疊為字面量3.實作的類是com.sun.tools.javac.comp.Attr類和com.sun.tools.javac.comp.Check類。

資料流分析

  資料流主要完成如下工作:

  • 檢查變量在使用前是否都已經被正确指派。
  • 保證final修飾的變量不會被重複指派。
  • 要确定方法的傳回值類型。這裡需要檢查方法的傳回值類型是否确定,并檢查接受這個方法傳回值的引用類型是否比對,如果沒有傳回值,則不能有任何引用類型指向方法的這個傳回值。
  • 所有的Checked Exception都要捕獲或者向上抛出。
  • 所有的語句都要被執行到。這裡會檢查是否有語句出現在一個return方法的後面,因為在return方法後面的語句永遠也不會被執行到。

控制流分析

  控制流主要完成如下工作:

  • 去掉無用的代碼,比如永假的if代碼塊。
  • 變量的自動轉換,比如自動裝箱拆箱。
  • 去除文法糖。解文法糖的過程由desugar()方法觸發,在com.sun.tools.javac.comp.TransTypes和com.sun.tools.javac.comp.Lower類中完成。

    資料流及控制流的分析入口是flow()方法,具體操作由com.sun.tools.javac.comp.Flow類來完成。

##位元組碼生成

  由com.sun.tools.javac.jvm.Gen類來完成。位元組碼階段不僅僅把前面各個步驟所生成的資訊(文法樹、符号表)轉化成位元組碼寫到磁盤中,編譯器還進行了少量的代碼添加和轉換工作。

執行個體構造器<init>方法和類構造器<clinit>方法就是在這個階段添加到文法樹中的。

  生成java位元組碼需要經過以下兩個步驟:

  • 将java方法中的代碼塊轉化成符合JVM文法的指令形式,JVM的操作都是基于棧的,所有的操作都必須經過出棧和進棧來完成。
  • 按照JVM的檔案組織格式将位元組碼輸出到以class為擴充名的檔案中。

  在jdk1.5之後,java語言提供了對注解(Annotation)的支援,這些注解與普通的Java代碼一樣,是在運作期間發揮作用的。在Jdk1.6中提供了一組插入式注解處理器的标準API在編譯期間對注解進行處理,我們可以把它看做是一組編譯器的插件,在這些插件裡面,可以讀取、修改、添加抽象文法樹中的任意元素。如果這些插件在處理注解期間對文法樹進行了修改,編譯器将回到解析及填充符号表的過程重新處理,直到所有插入式注解處理器都沒有再對文法樹進行修改為止。對注解的處理是在填充符号表之後及在标注注解之前發生的。

  1. 《深入了解Java虛拟機》周志明著。
  2. 《深入分析Java Web技術内幕》許令波著。
下一篇: qt編譯過程