Javac 源碼調試教程

爲何寫這這篇文章

一直有讀者問我 javac 源碼怎麼調試,本身也在寫 JVM 掘金小冊的過程當中閱讀了大量的 javac 的源碼,網上這方面的文章也比較少,那就來寫一篇 javac 源碼調試的文章吧,做爲 javac 系列文章的開篇。java

javac 源碼調試的過程是比較簡單的,它自己就是一個用 Java 語言寫的,對咱們理解內部邏輯比較友好。git

環境搭建過程

環境備註:Intellij、JDK8github

一、第一步下載導入 javac 的源碼算法

若是不想從 openjdk 下載折騰,能夠跳過第 1 步直接從個人 github 下載:github.com/arthur-zhan…bash

OpenJDK 的下載方式爲: 打開 hg.openjdk.java.net/jdk8/jdk8/l… ,點擊左側的 zip 或者 gz 進行下載。jvm

在 Intellij 中新建一個 javac-source-code-reading 項目,把源碼目錄的 src/share/classes/com 目錄整個拷貝到項目 src 目錄下,刪掉沒用的 javadoc 目錄。函數

二、找到 javac 主函數入口spa

代碼在src/com/sun/tools/javac/Main.java.net

運行這個 main 函數,由於沒有加須要編譯的源代碼路徑,不出意外應該會在控制檯會輸出下面的內容調試

新建一個HelloWorld.java文件,內容隨緣,在啓動配置的Program arguments里加入 HelloWorld.java 的絕對路徑。

再次運行 Main.java,會在 HelloWorld.java 的同級目錄生成 HelloWorld.class 文件。

三、加斷點

在 Main.java 中打上斷點,開始調試之後會發現無論怎麼設置,調試都會進入tool.jar,沒有走剛剛導入的源碼。

Intellij 中顯示的是反編譯 tools.jar 獲得的源碼,可讀性沒有源碼那麼好。

打開 Project Structure 頁面(File->Project Structure), 選中圖中 Dependencies 選項卡,把 <Moudle source> 順序調整到項目 JDK 的上面:

再次調試就已經能夠進入到項目源碼中的斷點處了。

javac 看字節碼案例一:tableswitch 和 lookupswitch 選擇的策略

讀者提問,下面的代碼編譯出的 switch-case 語句爲何採用了 lookupswitch,而不是 tableswitch,不是說「若是 case 的值比較緊湊,中間有少許斷層或者沒有斷層,會採用 tableswitch 來實現 switch-case」嗎?

public static void foo() {
    int a = 0;
    switch (a) {
        case 0:
            System.out.println("#0");
            break;
        case 1:
            System.out.println("#1");
            break;
        default:
        System.out.println("default");
            break;
    }
}
複製代碼

對應字節碼

public static void foo();
 0: iconst_0
 1: istore_0
 2: iload_0
 3: lookupswitch  { // 2
               0: 28
               1: 39
         default: 50
    }
複製代碼

這個問題比較有意思,主要是 tableswitch 和 lookupswitch 代價的估算,代碼在 src/com/sun/tools/javac/jvm/Gen.java

在 case 值只有 0 和 1 兩個值的狀況下

hi=1
lo=0
nlabels = 2

// table_space_cost = 4 + (1 - 0 + 1) = 6
long table_space_cost = 4 + ((long) hi - lo + 1); // words 

// table_time_cost = 3
long table_time_cost = 3; // comparisons

// lookup_space_cost = 3 + 2 * 2 = 7
long lookup_space_cost = 3 + 2 * (long) nlabels;

// lookup_time_cost = 2
long lookup_time_cost = nlabels;

// table_space_cost + 3 * table_time_cost  = 6 + 3 * 3 = 15
// lookup_space_cost + 3 * lookup_time_cost = 7 + 3 * 2 = 13
// opcode = 15 &lt;= 13 ? tableswitch : lookupswich

int opcode = nlabels &gt; 0 &&

table_space_cost + 3 * table_time_cost &lt;=
lookup_space_cost + 3 * lookup_time_cost

? tableswitch : lookupswitch;
複製代碼

因此在 case 值只有 0, 1 兩個的狀況下,代價的計算是 table_space_cost + 3 * table_time_cost > lookup_space_cost + 3 * lookup_time_cost,lookupswich代價更小選 lookupswich

若是有 0, 1,2 三個呢?

hi=2
lo=0
nlabels = 3

// table_space_cost = 4 + (2 - 0 + 1) = 7
long table_space_cost = 4 + ((long) hi - lo + 1); // words 

// table_time_cost = 3
long table_time_cost = 3; // comparisons

// lookup_space_cost = 3 + 2 * 3 = 9
long lookup_space_cost = 3 + 2 * (long) nlabels;

// lookup_time_cost = 3
long lookup_time_cost = nlabels;

// table_space_cost + 3 * table_time_cost  = 7 + 3 * 3 = 16
// lookup_space_cost + 3 * lookup_time_cost = 9 + 3 * 3 = 18
// opcode = 16 &lt;= 18 ? tableswitch : lookupswich

int opcode = nlabels &gt; 0 &&

table_space_cost + 3 * table_time_cost &lt;=
lookup_space_cost + 3 * lookup_time_cost

? tableswitch : lookupswitch;
複製代碼

因此在 case 值只有 0, 1,2 三個的狀況下,代價的計算是 table_space_cost + 3 * table_time_cost < lookup_space_cost + 3 * lookup_time_cost,tableswitch 代價更小選 tableswitch

其實在數量極少的狀況下,兩個的差異不大,只是 javac 這裏的算法致使選擇了 lookupswitch

javac 看字節碼案例二:加載整數到棧上的字節碼指令選擇

咱們知道有不少指令能夠把整數加載到棧上,好比iconst_0bipushsipushldc,那它們是如何選擇的呢?

public static void foo() {
        int a = 0;
        int b = 6;
        int c = 130;
        int d = 33000;
}

對應部分字節碼
 0: iconst_0
 1: istore_0
 2: bipush        6
 4: istore_1
 5: sipush        130
 8: istore_2
 9: ldc           #2                  // int 33000
11: istore_3
複製代碼

com/sun/tools/javac/jvm/Items.java 的 load() 函數加上斷點

能夠看到選擇的策略依次往下:

  • -1~5 之間選擇 iconst_n 的方式
  • -128~127 之間選擇 bipush
  • -32768~32767 之間的選擇 sipush
  • 其它大整數選擇 ldc

這與 java 虛擬機規範中字節碼指令文檔一致。

後記

用 javac 發掘不少有意思的東西,但願你能留言發現更好好玩的東東。

相關文章
相關標籤/搜索