跟隨Javac代碼來解答字節碼的疑惑

  前言  

  本文是跟隨掘金小冊張師傅的《JVM字節碼從入門到精通》練習而寫的。

 

  問題

  問題一:

  有如下代碼:

 1 package com.sun.tools.javac;
 2 
 3 /**
 4  * @author TY
 5  */
 6 public class Foo {
 7 
 8     public static void foo() {
 9         int a = 0;
10         int b = 6;
11         int c = 130;
12         int d = 33000;
13     }
14 
15 }

  很簡單的一段代碼,看似沒有什麼值得討論的地方,然而將代碼用javap查看下字節碼:

 

  可以看到針對定義的不同的變量,字節碼的指令是不同的,雖然都是整數。。。那爲什麼呢?簡單分析就可以得知,是和

聲明的值的大小有關係,針對不同的大小範圍採用不同的字節碼指令。但是如何驗證我們的想法呢?相信很多人是不知道的。下面會講到如何驗證

我們的想法。

 

 

  問題二:

  再有如下代碼:

 1 package com.sun.tools.javac;
 2 
 3 /**
 4  * @author TY
 5  */
 6 public class Switch {
 7 
 8     public static void foo() {
 9         int a = 0;
10         switch (a) {
11             case 0:
12                 System.out.println("#0");
13                 break;
14             case 1:
15                 System.out.println("#1");
16                 break;
17             default:
18                 System.out.println("default");
19                 break;
20         }
21     }
22 
23 }

  簡單的一段swtich...case邏輯,看着也沒有什麼問題,再看看字節碼:

 

  看到上面的字節碼,採用的是looupswitch,而我們知道,switch...case語句底層有兩種指令:tableswitch和looupswitch,之前我

在學習的時候就只是知道tableswtich適用於case值比較緊湊的情況,而lookupswitch適用於case值比較稀疏的情況。但是上面的代碼

case值分別是0和1,,,這這這,算哪門子稀疏了,所以爲什麼要用lookupswitch而不用tableswtich呢?

  

  Javac源碼

 

  我們知道將Java代碼編譯成字節碼的命令是Javac,所以關於上面的問題,都應該在Javac的源碼裏面去找答案。

  首先下載Javac的源碼,這裏我提供張師傅提供的地址:javac-source-code-reading

  下載好之後用IDEA打開:

  

 

 

  上面的out目錄是我自己建的,不然運行javac的入口main函數會報錯讓指定class碼輸出目錄。

  新建了目錄之後需要到項目設置裏面設置:

  

 

 

   

  然後將com.sun.tools下面的兩個包給排除掉,不然編譯通不過

  

  這些做好之後就可以執行下javac的入口函數: com.sun.tools.javac.Main

  直接執行來看看:

  

 

 

   執行結果:

  

 

 

   好熟悉的輸出,這不是在命令行裏面直接執行javac的輸出嗎?

  

  所以,知道該怎麼做了吧,需要加一個參數,參數就是要編譯的java類的名字,我新建一個Hello類,隨便寫點啥。。。

  

  然後將Hello類的路徑填寫到Main的傳入參數裏面:

  

 

 

  再執行Main函數就可以看到Hello類的同級目錄下就多了一個class文件:

  

 

 

   這就說明我們可以跟着Javac的源代碼進行調試了,不過在此之前需要設置下:

  

  將Module source移到系統安裝的JDK的前面,這樣才能正常的調試而不是調試的時候跳轉到安裝的JDK裏面去。

  可以調試Javac的代碼意味着很多東西,我們遇到不理解的字節碼的時候可以跟着Javac源碼走一遍,看人家到底是怎麼去實現的。

  接下來回到上面的兩個問題,對應的Javac的源碼分別在com.sun.tools.javac.jvm.Item類和com.sun.tools.javac.jvm.Gen類中,比如問題一代碼對應下面:

  

 

 

  可以看到針對值得大小不同走了不同的邏輯:

  -1到5:iconst_0

  -128到127:bipush

  -32768到32767:sipush

  然後超過以上的範圍則是:ldc

 

  而問題二對應的代碼在Gen類visitSwitch方法中,重點代碼是下面這段:

  

 

  hi和lo是在的代碼邏輯中分別指向case的最大和最小值。我們將case值0和1代入其中:

  最終table_space_cost + 3 * table_time_cost = 15,而looup_space_cost + 3 * looup_time_cost = 13,

所以最終選用了lookupswitch指令。關於爲什麼要採用這樣的算法我暫時不知,但至少對照着Javac源碼知道了算法邏輯,不至於遇到

問題的時候一臉懵。。

  

  總結

  本文主要講述瞭如何調試Javac源碼以及如何運用Javac源碼解答一些字節碼的問題。

  另外,推薦掘金小冊張師傅的小冊《JVM字節碼從入門到精通》

  可以掃描下面我分享的二維碼購買,會便宜幾塊錢。。。(土豪不用管)

  

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