jvm - 類的編譯

編譯

咱們都知道,當咱們編寫完代碼後,能夠用javac命令或者開發工具,好比eclipse、idea等,把java文件編譯成class文件,java虛擬機才能夠執行。下圖是WinHex打開class文件16進制的字節碼。
image.png
每一個class文件的前面四個字節成爲魔數,它的惟一做用就是肯定這個文件是否能被虛擬機接受的文件,這個魔數值爲0xCSAFEBABE(咖啡寶貝)。第五、6字節是次版本號,第七、8字節是主版本號。從上圖能夠看到,個人主版本號是16進制的34,也就是10進制的52,52對應的JDK版本是1.8。
後面的16進制在這裏不作過多的講解,能夠根據Java虛擬機規範的約定,以無符號數和表兩種數據類型進行解析。
java文件編譯成class文件的時候,先根據詞法和語法分析得到了程序代碼的抽象語法樹表示,填充符號表,而後根據語義分析看程序是否符合邏輯,好比變量在使用前是否已經聲明,變量是否正確賦值等,最後就是把前面步驟生成的信息(好比語法樹),轉換成字節碼寫到磁盤中,固然編譯器還作了少許的代碼添加(程序中沒有構造函數,會添加無參構造函數的操做是在填充符號表完成的,不是這裏完成)和轉換工做。java

編譯優化

常量摺疊

請看下面的例子:eclipse

public class HelloWord {
     public static void main(String[] args) {
           System.out.println("Hello," + " World");
     }
}

若是我把上面的代碼,改爲這樣的,那麼我在運行期的時候,會加劇虛擬機的負擔嗎?答案是否認的。編譯器除了檢查外,還對常量進行了摺疊,也就是說,上面的Hello World會在編譯的時候自動拼接,若是是int型的,好比i = 1 + 2,等價於 i = 3,並不會增長程序在運行期哪怕一個CPU指令的運算量。
咱們看看下面這個,若是是str += 「c」呢,會不會摺疊呢?ide

public void fun() {
    String str = "a" + "b";
    str += "c";
    System.out.println(str);
}

咱們從下圖的ASTView能夠看出,標記1和標記2是不同的類型,一個是變量賦值,第二個是表達式,在標記3能夠看到,變量賦值的時候,自動把a和b拼接起來。
image.png函數

泛型的擦除

看下面的例子:工具

public static void main(String[] args) {
    List<String> list1 = new ArrayList<>();
    List<Map<String, Integer>> list2 = new ArrayList<>();
    System.out.println(list1);
    System.out.println(list2);
}

反編譯後,以下,竟然泛型還在,其實這個出乎個人意料以外,兩年前給公司分享虛擬機的時候,個人ppt裏還記錄着泛型的擦除的案例,反編譯後確實的沒有泛型的。開發工具

public static void main(String[] args) {
    List<String> list1 = new ArrayList<String>();
    List<Map<String, Integer>> list2 = new ArrayList<Map<String, Integer>>();
    System.out.println(list1);
    System.out.println(list2);
  }

因而我又換了另一個反編譯器,反編譯以下,此次確實沒有泛型信息。優化

public static void main(String[] args)
  {
    List list1 = new ArrayList();
    List list2 = new ArrayList();
    System.out.println(list1);
    System.out.println(list2);
  }

用java自帶的命令javap -verbose進行反編譯,結果以下,能夠看到泛型信息在只有調試用的LocalVariableTypeTable(LVTT)裏,某些反編譯器應該根據LVTT來展現泛型信息。idea

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      31     0  args   [Ljava/lang/String;
    8      23     1 list1   Ljava/util/List;
   16      15     2 list2   Ljava/util/List;
LocalVariableTypeTable:
Start  Length  Slot  Name   Signature
    8      23     1 list1   Ljava/util/List<Ljava/lang/String;>;
   16      15     2 list2   Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/Integer;>;>;

由於泛型在編譯期就會被擦除,因此如下的重載,是編譯不過去的,由於擦除後,他們其實spa

public static String fun(List<String> list) {
    return null;
}

public static Integer fun(List<Integer> list) {
    return null;
}

自動裝箱、拆箱與循環遍歷

這部分代碼包括自動裝箱、拆箱與循環遍歷。調試

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

反編譯後以下,首先把int裝箱變成Integer,而後計算的時候,再從Integer拆卸變成int。另一個就是把for編譯成Iterator迭代器。

public void fun()
  {
    List 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);
  }

另外看看下面的例子

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 4;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    System.out.println(c == d);
    System.out.println(e == f);
    System.out.println(c == (a + b));
    System.out.println(c.equals(a + b));
    System.out.println(g == (a + b));
    System.out.println(g.equals(a + b));
}

反編譯後

public static void main(String[] args)
  {
    Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    Integer c = Integer.valueOf(3);
    Integer d = Integer.valueOf(4);
    Integer e = Integer.valueOf(321);
    Integer f = Integer.valueOf(321);
    Long g = Long.valueOf(3L);
    //爲false,Integer對象,比較地址,兩個地址不同
    System.out.println(c == d);
    //爲false,由於兩個地址不同
    System.out.println(e == f);
    //爲true,都變成int,比較值
    System.out.println(c.intValue() == a.intValue() + b.intValue());
   //爲true,equals方法比較int System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
   //爲true,比較值
    System.out.println(g.longValue() == a.intValue() + b.intValue());
    //爲false,類型不同
    System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
  }

條件編譯

下面這個包括沒有使用的變量a,if語句判斷,while語句。

public static void main(String[] args) {
    int a;
    if(true){
        System.out.println("Hello");
    }else{
        System.out.println("World");
    }
   /* while(false){
        // Unreachable statement
        System.out.println("while");
    }*/
}

反編譯後,沒有使用的變量a並無被編譯,Dead code,也就是爲false的部分也沒有被編譯,另外while那個,因爲恆爲false,編譯器報Unreachable statement。

public static void main(String[] args)
  {
    System.out.println("Hello");
  }
相關文章
相關標籤/搜索