1. 程式人生 > >【Java虛擬機器】Java前端編譯器

【Java虛擬機器】Java前端編譯器

Java前端編譯器

關於編譯器

  • 前端編譯器:把java檔案轉化為class檔案的過程
  • 執行時編譯器(JIT編譯器,Just In Time Compiler):把位元組碼轉化為機器碼的過程
  • 靜態提前編譯器(AOT編譯器, Ahead Of Time Compiler):直接把class檔案編譯成機器程式碼的過程。

編譯過程

解析與填充符號表

詞法、語法分析

詞法分析是將原始碼的字元流轉變為標記集合,關鍵字、變數名、字面量、運算子都可以成為標記,如“int a = b + 2”這句程式碼包含了6個標記,分別是int、a、=、b、+、2。

語法分析是根據標記集合構造抽象語法樹的過程,抽象語法樹是一種用來描述程式程式碼語法結構的樹形表示方式。經過這個步驟之後,編譯器基本不會再對原始碼檔案進行操作了,後續的操作都建立在抽象語法樹之上。

填充符號表

符號表示由一組符號地址和符號資訊構成的表格,在編譯的不同階段都要用到。在語義分析中,將用於語義檢查(如檢查一個名字的使用和原先的說明是否一致)和產生中間程式碼。在目的碼生成階段,當對符號名進行地址分配時,符號表示地址分配的依據。

註解處理器

提供了一組插入式註解處理器的標準API在編譯期間對註解進行處理。

可以讀取、修改、新增抽象語法樹中的元素。如果對語法樹進行了修改,將回到解析及填充符號表的過程重新處理,直到不再對語法樹進行修改為止。

語義分析與位元組碼生成

標註檢查

  • 檢查變數使用前是否已被宣告
  • 變數與賦值之間的資料型別是否能夠匹配
    … …

資料及控制流分析

  • 程式區域性變數使用前是否有賦值
  • 方法的每條路徑是否都有返回值
  • 是否所有的受查異常都被正確處理
    … …

解語法糖

  • 泛型
  • 變長引數
  • 自動裝箱/拆箱
    … …

位元組碼生成

  • 例項構造器的生成
  • 類構造器的生成
    … …

Java語法糖

泛型與型別擦除

public static void main(String[] args) {
	Map<String, String> map = new HashMap<>();
	map.put("hello", "你好");
	map.put("how are you?", "吃了沒?");
	System.out.println(map.get("hello"));
	System.out.println(map.get("how are you?"));
}

其實編譯後的是這樣的

 public static void main(String[] args) {
	  Map<String, String> map = new HashMap();
	  map.put("hello", "你好");
	  map.put("how are you?", "吃了沒?");
	  System.out.println((String)map.get("hello"));
	  System.out.println((String)map.get("how are you?"));
 }

由於Java的泛型是擦除的,所有對於執行期的Java語言來說,ArrayList<Integer>ArrayList<String>就是同一個類

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

public static void main(String[] args) {
	List<Integer> list = Arrays.asList(1, 2, 3, 4);
	int sum = 0;
	for (int i : list) {
		sum += i;
	}
	System.out.println(sum);
}

其實編譯後的是這樣的

public static void main(String[] args) {
	List<Integer> list = Arrays.asList(
			new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
	int sum = 0;
	for (Iterator localIterator = list.iterator(); localIterator.hasNext();) {
		int i = ((Integer) localIterator.next()).intValue();
		sum += i;
	}
	System.out.println(sum);
}

遍歷迴圈把程式碼還原成了迭代器的實現,這就是為何遍歷迴圈需要被遍歷的類實現Iterable介面的原因。

參考

  1. 深入理解Java虛擬機器[書籍]