Javac編譯原理

1、javac作用
· 將*.java源代碼文件轉化爲*.class文件
2、編譯流程
 
流程:
· 詞法分析器:將源碼轉換爲Token流
· 將源代碼劃分成一個個Token(Token包含的元素類型看3.2)
· 語法分析器:將Token流轉化爲語法樹
· 將上述的一個個Token組成一句句話(或者說成一句句代碼塊),檢查這一句句話是不是符合Java語言規範
· 語義分析器:將語法樹轉化爲註解語法樹
· 將複雜的語法轉化成簡單的語法(eg.註解、foreach轉化爲for循環)並做一些檢查,添加一些代碼
· 代碼生成器:將註解語法樹轉化爲字節碼
3、詞法分析
3.1、作用
· 將源碼轉換爲Token流。
3.2、流程
一個字節一個字節的讀取源代碼,形成規範化的Token流。規範化的Token包含:
·java關鍵詞:package、import、public、class、int等
·自定義單詞:包名、類名、變量名、方法名
·符號:=、;、+、-、*、/、%、{、}等
3.3、示例
代碼:
package compile;
/**
 * 詞法
 */
public class Cifa {
int a;
int c = a + 1;
}
以上代碼轉化爲的Token流:
 
說明:完成以上示例的是JavacParser的parseCompilationUnit()方法,源代碼見文章開頭的書籍。
注意:上邊的token流符合java語言規範。
3.4、疑問
· 怎樣判斷package是java關鍵詞還是自定義變量?
· JavacParser會根據java語言規範來控制什麼順序、什麼地方出現什麼Token(這個查看parseCompilationUnit()源碼就知道了),所以package在文件的最開頭出現,我們會知道是一個Token.PACKAGE類型,而非自定義的Token.IDENTIFIER類型。
· 一條實踐:在編寫程序的時候,不要用java關鍵詞來定義變量名、類名、包名、方法名,而是採取一定有意義的單詞來定義,當然,你再eclipse中編寫代碼的時候,如果使用了java關鍵詞來定義變量,eclipse會提醒你這是一個錯誤的定義。
· 怎樣確定package是一個Token,而packa不是?
· 我的理解是,主要看空格和符號(符號見3.2),對於package是一個單詞,中間沒有空格也沒有符號,所以是一個Token
· 一條實踐:在編寫代碼時,例如:int a = b + c;//a與=中間有一個空格、=與b之間有一個空格、b與+之間有一個空格、+與c之間有一個空格,當然,這裏沒有空格也行,因爲每一個變量之間正好都是由符號來隔開的,但是之前看了一個視頻說,如果上邊這句話沒有這些空格的話,可能編譯不通過,所以我們最好還是加上空格,當然加上空格後顯得整個代碼也清晰。
 
4、語法分析
4.1、作用
· 將進行詞法分析後形成的Token流中的一個個Token組成一句句話,檢查這一句句話是不是符合Java語言規範。
4.2、語法分析三部分:
· package
· import
· 類(包含class、interface、enum),一下提到的類泛指這三類,並不單單是指class
4.3、示例
代碼:
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;
}
}
最終語法樹:
 
說明:
· 每一個包package下的所有類都會放在一個JCCompilationUnit節點下,在該節點下包含:package語法樹(作爲pid)、各個類的語法樹
· 每一個從JCClassDecl發出的分支都是一個完整的代碼塊,上述是四個分支,對應我們代碼中的兩行屬性操作語句和兩個方法塊代碼塊,這樣其實就完成了語法分析器的作用:將一個個Token單詞組成了一句句話(或者說成一句句代碼塊)
· 在上述的語法樹部分,對於屬性操作部分是完整的,但是對於兩個方法塊,省略了一些語法節點,例如:方法修飾符public、方法返回類型、方法參數。
疑問:
import節點的語法樹與package的相似,但是import語法樹放在了哪一個地方?
5、語義分析
5.1、作用
· 將語法樹轉化爲註解語法樹
5.2、步驟
·添加默認的無參構造器(在沒有指定任何有參構造器的情況下)
  · 處理註解
  · 標註:檢查語義合法性、進行邏輯判斷
  · 檢查語法樹中的變量類型是否匹配(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,內部類解析成一個與外部類相關聯的外部類)
  · 最後,將經過上述處理的語法樹轉化爲最後的註解語法樹
6、生成字節碼
6.1、作用
· 將註解語法樹轉化成字節碼,並將字節碼寫入*.class文件。
6.2、步驟
· 將java的代碼塊轉化爲符合JVM語法的命令形式,這就是字節碼
· 按照JVM的文件組織格式將字節碼輸出到*.class文件中
具體的源代碼與步驟查看com.sun.tools.javac.jvm.Gen類與《分佈式Java應用:基礎與實踐》P42
6.3、class文件包含的內容
在生成的*.class文件中不只包含字節碼信息,具體包含:
· 結構信息
· class文件格式版本號
· 各部分的數量與大小
· 元數據
· 類、父類、實現接口的聲明信息
· 屬性聲明信息
· 方法聲明信息
· 常量池
· 方法信息
· 字節碼
· 異常處理器表
· 局部變量區的大小
· 操作數棧的大小
· 操作數棧的類型記錄
· 調試用符號信息
這裏提到的局部變量區和操作數棧組成了了方法棧,可以參看第一章 JVM內存結構
總結:
 對於編譯這一塊兒,我們在實際操作中不會直接去操作這些代碼,不像類加載器機制,我們可能需要自己編寫類加載工具,也不像Java內存管理那樣,我們會直接在服務器配置堆棧方法區空間、配置GC收集器等,但是瞭解javac編譯,對於我們瞭解以後的類文件結構、類加載機制有一定的幫助,也有利於我們掌握整個Java代碼的執行流程,對於我們瞭解編譯期間編譯器做的一些檢查工作也有很大幫助,瞭解這些檢查工作有利於我們在寫代碼的時候更加小心,例如,檢查型異常都需要捕獲或拋出,每一條語句都要被執行到(即可達)等。雖然,這些工作eclipse會在我們寫代碼的時候爲我們自動去檢查,包括檢查語句是否可達,但是瞭解這些還是有好處的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章