java源代碼(符合語言規範)-->javac-->.class(二進制檔案)-->jvm-->機器語言(不同平台不同種類)
如何讓java的文法規則适應java虛拟機的文法規則?這個任務由javac編譯器來完成java語言規範轉換成java虛拟機語言規範。
編譯流程:

流程:
詞法分析器:将源碼轉換為Token流
将源代碼劃分成一個個Token(找出java語言中的if,else,for等關鍵字)
文法分析器:将Token流轉化為文法樹
将上述的一個個Token組成一句句話(或者說成一句句代碼塊),檢查這一句句話是不是符合Java語言規範(如if後面跟的是不是布爾判斷表達式)
語義分析器:将文法樹轉化為注解文法樹
将複雜的文法轉化成簡單的文法(eg.注解、foreach轉化為for循環、去掉永不會用到的代碼塊)并做一些檢查,添加一些代碼(預設構造器)
代碼生成器:将注解文法樹轉化為位元組碼(即将一個資料結構轉化成另一個資料結構)
ps:要擷取javac編譯器,可以通過OpenJDK來下載下傳源碼,可以自己編譯javac的源碼,也可以通過調用jdk的com.sun.tools.javac.main.Main類來手動編譯指定的類。Javac編譯動作的入口是com.sun.tools.javac.main.JavaCompiler類,代碼邏輯集中在這個類的compile()和compile2()方法中,整個編譯最關鍵的處理就由圖中标注的8個方法來完成,
目的:将源碼轉換為Token流
一個字元一個字元的讀取源代碼,形成規範化的Token流。規範化的Token包含:
java關鍵詞:package、import、public、class、int等
自定義單詞:包名、類名、變量名、方法名
符号:=、;、+、-、*、/、%、{、}等
源碼關鍵:
詞法分析過程是在的JavacParser.parseCompilationUnit()中完成的
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類負責此任務。
例子:
以上代碼轉化為的Token流:
目的:将進行詞法分析後形成的Token流中的一個個Token組成一句句話,檢查這一句句話是不是符合Java語言規範。
package
import
類(包含class、interface、enum),一下提到的類泛指這三類,并不單單是指class
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等
最終文法樹
ps:左邊還少一個import的文法節點
說明:
每一個包package下的所有類都會放在一個JCCompilationUnit節點下,在該節點下包含:package文法樹(作為pid)、各個類的文法樹
每一個從JCClassDecl發出的分支都是一個完整的代碼塊,上述是四個分支,對應我們代碼中的兩行屬性操作語句和兩個方法塊代碼塊,這樣其實就完成了文法分析器的作用:将一個個Token單詞組成了一句句話(或者說成一句句代碼塊)
在上述的文法樹部分,對于屬性操作部分是完整的,但是對于兩個方法塊,省略了一些文法節點,例如:方法修飾符public、方法傳回類型、方法參數。
目的:将文法樹轉化為注解文法樹
添加預設的無參構造器(在沒有指定任何有參構造器的情況下),把引用其他類的方法或者變量,抑或是繼承實作來的變量和方法等輸入到類自身的符号表中
處理注解
标注:檢查語義合法性、進行邏輯判斷
檢查文法樹中的變量類型是否比對(eg.String s = 1 + 2;//這樣"="兩端的類型就不比對)
檢查變量、方法或者類的通路是否合法(eg.一個類無法通路另一個類的private方法)
變量在使用前是否已經聲明、是否初始化
常量折疊(eg.代碼中:String s = "hello" + "world",語義分析後String s = "helloworld")
推導泛型方法的參數類型
資料流分析
變量的确定性指派(eg.有傳回值的方法必須确定有傳回值)
final變量隻能賦一次值,在編譯的時候再指派的話會報錯
所有的檢查型異常是否抛出或捕獲
所有的語句都要被執行到(return後邊的語句就不會被執行到,除了finally塊兒)
進一步語義分析
去掉永假代碼(eg.if(false))
變量自動轉換(eg.int和Integer)自動裝箱拆箱
去掉文法糖(eg.foreach轉化為for循環,assert轉化為if,内部類解析成一個與外部類相關聯的外部類)
最後,将經過上述處理的文法樹轉化為最後的注解文法樹
com.sun.tools.javac.comp.Enter 将java類中的符号輸入到符号表中,主要是兩個步驟:
将所有類中出現的符号輸入到類自身的符号表中,所有類符号、類的參數類型符号(泛型參數類型)、超類符号和繼承的接口類型符号等都存儲到一個未處理的清單中。
将這個未處理的清單中所有的類都解析到各自的類符号清單中,這個操作是在MemberEnter.complete()中完成(預設構造器也是在這裡完成的)。
com.sun.tools.javac.processing.JavacProcessingEnvironment 處理注解
com.sun.tools.javac.comp.Attr 檢查語義的合理性并進行邏輯判斷,類型是否比對,是否初始化,泛型是否可推導,字元串常量合并
com.sun.tools.javac.comp.Check 協助attr,變量類型是否正确
com.sun.tools.javac.comp.Resolve 協助attr,變量方法類的通路是否合法,是否是靜态變量
com.sun.tools.javac.comp.ConstFold 協助attr,常量折疊
com.sun.tools.javac.comp.Infer 協助attr,推導泛型
com.sun.tools.javac.comp.Flow 資料流分析和替換等價源代碼的分析(即上面的進一步語義分析)
目的:将注解文法樹轉化成位元組碼,并将位元組碼寫入*.class檔案。
将java的代碼塊轉化為符合JVM文法的指令形式,這就是位元組碼
按照JVM的檔案組織格式将位元組碼輸出到*.class檔案中
com.sun.tools.javac.jvm.Gen 周遊文法樹生成最終的java位元組碼
com.sun.tools.javac.jvm.Items 輔助gen,這個類表示任何可尋址的操作項,這些操作項都可以作為一個機關出現在操作棧上
com.sun.tools.javac.jvm.Code 輔助gen,存儲生成的位元組碼,并提供一些能夠影射操作碼的方法