Java 的編譯器有三類前端
下面簡要介紹下 Java 的編譯與優化java
[TOC]程序員
幾乎是全部編譯器的第一步,通過這一步,編譯器能夠生成初始語法樹,由於註解的存在,最終語法樹的生成在註解處理以後數組
Java 的泛型是僞泛型緩存
C++ 中的模板是泛型的一種形式,編譯時不一樣類型的泛型參數會促使模板生成新的代碼,這種經過類型膨脹的形式實現的泛型是真實泛型函數
Java 的泛型只展示在代碼中,編譯成字節碼後泛型信息就已經丟失了(底層使用 Object 引用實際對象,實際使用時會有強制類型轉換的過程),下面兩行代碼在相同的 java 文件中時是沒法經過編譯的,由於 class 文件中不容許出現簽名相同的函數(此時泛型信息已經丟失)性能
public void method(List<String> list) { ... } public void method(List<Integer> list) { ... }
在 C++ 中,上面的兩個函數在編譯時的簽名是不一樣的,但在 Java 的 class 文件中,這兩個函數的簽名相同,故編譯器報錯優化
class 格式規定,只要描述符不徹底一致的兩個方法就能夠共存線程
Java 中類型的描述符是包含返回值的,也就是說只要修改上面那兩個方法中的返回值,這兩個方法就能夠共存,這是設計中的 缺陷,雖然對程序的正常運行沒有影響,但違反了返回值不參與重載的規定設計
Java 新標準中提出了一些新的規範來減小這種狀況對語言的影響,如 Signature 等
拆箱與裝箱的陷阱
public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); // true,Java的==比的是引用地址,小整數有緩存故地址相同 System.out.println(e == f); // false,默認狀況下 Java 緩存 -128~127 之間的全部整數 System.out.println(c == (a + b)); // true,有運算符,故先拆箱後比較 System.out.println(c.equals(a + b)); // true,同類比較 System.out.println(g == (a + b)); // true,包裝類的 == 只會在遇到算術運算符時拆箱 System.out.println(g.equals(a + b)); // false,異類比較,equals不處理類型轉換,故long!=int }
Java 的條件編譯很簡單,只能使用 if 且 if 的條件必須爲常量,編譯後 class 文件中只會留下知足需求的語句塊
JDK 5 以後 Java 提供了對註解的支持,Java 支持編譯時註解和運行時註解,使用編譯時註解咱們能夠干涉編譯器的行爲
部分商用虛擬機中 Java 程序最開始執行時使用解釋器進行解釋執行,若是發現一塊代碼執行頻繁,JVM 會使用 JIT (即時編譯)對其進行編譯,轉化爲平臺相關的機器碼並進行各類優化以提升執行效率
HotSpot 有兩個即時編譯器,分別稱爲 Client Compiler 和 Server Compiler,或者簡稱 C1 和 C2 編譯器,JVM 會根據運行模式選擇不一樣的編譯器,用戶能夠經過 -client 或者 -server 指定編譯器。固然也能夠強制禁止 JVM 使用運行時編譯器,JVM 全程使用解釋方式運行
OSR(On Stack Replacement),JIT 編譯並優化某個方法的形象說法,優化的方法都位於棧,方法優化即替換已有的解釋執行方法,即棧上替換
判斷一個函數是否是熱點代碼片斷有兩種方式:基於採樣的熱點探測和基於計數器的熱點探測。前者 JVM 週期性的檢查棧頂函數類型,後者 JVM 會爲每個函數維護一個計算器,後者更精確嚴謹些
HotSpot 使用第二種方式並使用了兩類計數器:
當前大部分 JVM 設計都把代碼的優化重心放在了 JIT 上,除非特殊狀況,不要關閉 JIT
JIT 編譯使用了大量編譯技術,本文不作過多介紹,下面僅給出幾個便於理解的方法
C++ 爲靜態編譯語言而 Java 爲動態編譯,兩者編譯器各有優劣。C++ 能夠在編譯時進行比較耗時的優化而Java卻不行,由於這樣會影響服務性能;Java 能夠蒐集大量運行時信息(調用頻率、分支頻率預測、裁剪未選中分支等等)來優化代碼但 C++ 不行。其餘對比能夠查閱周志明《深刻理解 Java 虛擬機》