1. 程式人生 > >Java虛擬機器 程式編譯

Java虛擬機器 程式編譯

主要內容

javac編譯器

java語法糖

編譯期

概述

Java 語言的“編譯期”可能為以下3中編譯過程:

前端編譯器:即編譯器的前端,把.java檔案轉變為.class檔案。

JIT編譯器:把位元組碼轉變為機器碼。

AOT編譯器:直接把.java轉變為本地機器碼。

javac編譯器

10.2.1 javac的原始碼與除錯



解析與符號填充

詞法分析:將原始碼的字元流轉為標記(token)集合。如”int a = b+2”這句程式碼中包含了6個標記int、a、=、b、+、2。Javac原始碼中詞法分析由com.sun.tools.javac.parser.Scaner類來實現。

語法分析:根據token序列來構建抽象語法樹的過程,抽象語法樹是一種描述程式語法結構的樹形表示方式,語法樹的每一個節點都代表著程式程式碼中的一個語法結構,例如包、型別、修飾符、運算子、介面、返回值甚至註釋都可以是一個語法結構。

填充符號表:語法詞法分析之後就是填充符號表階段。符號表是由一組符號地址和符號資訊構成的表格,其中的資訊在編譯的不同階段都會用到,在語義分析中用於語義檢查和產生中間程式碼,在目的碼生成階段是符號名地址分配的依據。

註解處理器

註解與普通程式碼一樣是在執行期間發揮的作用,可以把它看做編譯器的外掛,可以讀取、修改、新增抽象語法樹中的任意元素。如果這些外掛在處理期間對語法樹進行了修改,那麼編譯器將回到解析及填充符號表的過程重新處理。可以使用註解實現許多原本只能在編碼中完成的事。

語義分析與位元組碼生成

語法分析後,程式獲得了程式碼的抽象語法樹,語法樹能表示一個結構正確的源程式的抽象,但是不能保證符合邏輯。而語義分析的主要任務是保證結構正確的程式進行上下文有關性質的審查,如型別審查。

語義分析包括標註檢查和資料及控制流分析。

1.      標註檢查的內容包括變數使用前是否被宣告,變數與賦值之間型別是否匹配等等。還有個特殊動作常量摺疊。Int a = 1+2;摺疊後會在語法樹上被標註為字面量3。

2.      資料及控制流分析是對邏輯進一步驗證,如區域性變數使用前是否賦值、方法的每條路徑是否都有返回值等。編譯器與類載入時的資料及控制流分析基本一致,只是校驗範圍不同。如final修飾的變數,編譯後與沒有修飾的class檔案沒有任何區別。所以final修飾的變數在執行期沒有任何影響,不變性僅在編譯期由編譯器保證。

3.      解語法糖

4.      位元組碼生成是javac編譯過程的最後階段,不僅僅把前面各個步驟生成的資訊(語法樹、符號表)轉化成位元組碼儲存到磁碟中,編譯器還進行了少量程式碼新增和轉化。比如新增預設構造方法、優化程式的實現邏輯,把string相加操作變為stringbuilder等。

Java語法糖的味道

泛型與型別擦除

兩個方法有不同的返回值居然過載成功了!說明這個過載不是根據返回值來確定的,由於泛型引入可能對原有基礎產生影響和新的需求,所以JCP組織對虛擬機器規範做出了相應的修改,引入了新屬性用於解決引數型別識別問題,包括儲存一個方法在位元組碼層面的特徵簽名。擦除只是對方法的Code屬性中位元組碼進行擦除,實際上元資料中還是保留了泛型資訊,這也是我們能通過反射取得引數化型別的依據。

自動裝箱、拆箱與遍歷迴圈


包裝類的“==”運算在沒有遇到算數運算的情況下不會自動拆箱,而且equals()方法不會處理資料型別轉換關係。

條件編譯

執行期

Java程式最初是通過直譯器進行解釋執行的,當虛擬機發現某個方法或程式碼塊執行特別頻繁,就會把這些程式碼認定為“熱點程式碼”,為了提高執行效率,執行時虛擬機器會將這些程式碼編譯成與本地平臺相關的機器碼,並進行各個層次的優化,完成這個任務的編譯器叫即時編譯器,也稱為JIT編譯器。

HotSpot虛擬機器內的即時編譯器

直譯器與編譯器

Java虛擬機器大多采用直譯器與編譯器並存的架構,當程式需要迅速啟動和執行的時候直譯器可以先發揮作用,省去編譯時間立即執行;當程式執行後隨著時間的推移編譯器逐漸發揮作用,把越來越多的程式碼編譯成原生代碼並獲得更高的執行效率。

編譯優化技術

以編譯方式執行原生代碼比解釋方式更快,除去虛擬機器解釋執行位元組碼的額外消耗,JDK設計團隊幾乎把對程式碼所有的優化措施都集中在了即時編譯器。

11.3.2 公共子表示式消除

如果一個表示式E已經被計算過了,並且從先前的計算到現在所有變數值都沒有發生變化,那麼E這次出現就成為了公共子表示式,即沒有必要再次計算直接用之前的表示式結果替代E就行了。

11.3.3 陣列邊界檢查消除

Java中訪問陣列元素時會進行上下界範圍檢查,否則會丟擲執行時異常,這對於開發者來說是好事,可以防止很多溢位攻擊,但是每次檢查會造成效能負擔。

所以只要在編譯期根據資料流分析確定陣列長度,並判斷下標沒有越界,那在執行的時候就無需判斷了。

與語言相關的其他消除操作還有自動裝箱消除、安全點消除、消除反射等。

11.3.4 方法內聯

除了消除方法呼叫的成本外,更重要的是為其他優化手段建立良好基礎。


方法內聯的優化看起來簡單,不過是把目標方法的程式碼“複製”到發起呼叫的方法之中從而避免發生真實呼叫而已,但如果不是編譯器做了一些特別努力,按照經典編譯的優化理論大多數java方法都無法內聯。

對於一個虛方法(預設的例項方法就是虛方法),編譯器做內聯的時候無法確定使用哪個版本的方法。由此引入了”型別繼承關係分析”技術來確定介面是否有多於一種實現,某個類是否有子類且子類是否抽象等資訊。

在進行內聯時,如果是非虛方法則直接內聯;如果是虛方法且分析後只有一個版本則也可以內聯。但是這種內聯比較激進,如果載入了導致繼承關係發生變化的新類,則退回解釋狀態執行,或重新進行編譯。