Java 程式設計師鮮少關注編譯結果。事實上,位元碼提供極有價值的資訊,尤其在除錯效能問題時,能更深入理解如何提升執行效率。JDK 讓位元碼分析變得容易。本文涵蓋:
javapJava 程式編譯為中間位元碼,由 JVM 解釋執行。javap 是 JDK 提供的命令列工具,對 .class 檔案反組譯,輸出可讀版本。預設列印公有欄位、方法、建構子、靜態初始化區塊。
| 選項 | 說明 |
|---|---|
-c | 反組譯程式碼,印出所有方法的 JVM 指令(含 private/protected) |
-l | 顯示行號與區域變數表 |
-verbose | 印出堆疊大小、區域變數/參數數量、驗證資訊 |
-private | 顯示所有類別與成員 |
-classpath <path> | 指定類別檔案搜尋路徑 |
public class ExampleOfByteCode {
public ExampleOfByteCode() { }
public static void main(String[] args) {
System.out.println("Hello world");
}
}
執行 javap -c ExampleOfByteCode 輸出:
Compiled from ExampleOfByteCode.java
public class ExampleOfByteCode extends java.lang.Object {
public ExampleOfByteCode();
public static void main(java.lang.String[]);
}
Method ExampleOfByteCode()
0 aload_0
1 invokespecial #6 <Method java.lang.Object()>
4 return
Method void main(java.lang.String[])
0 getstatic #7 <Field java.io.PrintStream out>
3 ldc #1 <String "Hello world">
5 invokevirtual #8 <Method void println(java.lang.String)>
8 return
getstatic:將靜態欄位推入運算元堆疊,#7 指向常數池第 7 項ldc:載入常數 "Hello world" 至堆疊invokevirtual:呼叫 println,從堆疊彈出兩個參數(字串 + 隱含 this)Java 被稱為網際網路安全語言,安全核心在於避免記憶體錯誤。位元碼是第一道防線。
public float add(float f, int n) { return f + n; }
javap -c 輸出:
Method float add(float, int)
0 fload_1
1 iload_2
2 i2f
3 fadd
4 freturn
fload_1、iload_2 依型別前綴(f/i)載入對應型別值aastore/aaload 強制檢查邊界,越界拋出 ArrayIndexOutOfBoundsExceptionif* 僅能在同一方法內跳轉,控制權轉移僅能透過 return、例外、invoke,關閉惡意跳轉大門了解編譯器如何翻譯程式碼,有助撰寫更高效程式,甚至防止隱性錯誤。
// 方式 1:String 串接
String concat(String str1, String str2) { return str1 + str2; }
// 方式 2:StringBuffer
void concat(StringBuffer str1, String str2) { str1.append(str2); }
javap -c 對比:
// String 版本:5 次方法呼叫
Method java.lang.String concat(java.lang.String, java.lang.String)
0 new #6 <Class java.lang.StringBuffer>
3 dup
4 aload_1
5 invokestatic #14 <Method java.lang.String valueOf(java.lang.Object)>
8 invokespecial #9 <Method java.lang.StringBuffer(java.lang.String)>
11 aload_2
12 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)>
15 invokevirtual #13 <Method java.lang.String toString()>
18 areturn
// StringBuffer 版本:1 次呼叫
Method void concat(java.lang.StringBuffer, java.lang.String)
0 aload_1
1 aload_2
2 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)>
5 pop
6 return
String 不可變,串接需建立 StringBuffer、append、toString,顯著較慢。不同編譯器(如 JBuilder)優化程度不同,javap 可清晰對比。
注意:JIT(即時編譯器)在方法首次呼叫時將位元碼重編譯為機器碼,可進行更多優化。除非擁有 VM 原始碼,應以效能基準測試輔助位元碼分析。
public class ChangeALot {
public static final boolean debug = false;
public static boolean log = false;
}
public class EternallyConstant {
public static void main(String[] args) {
System.out.println("EternallyConstant beginning execution");
if (ChangeALot.debug) System.out.println("Debug mode is on");
if (ChangeALot.log) System.out.println("Logging mode is on");
}
}
修改 ChangeALot 將兩欄位設為 true,僅重編譯 ChangeALot 再執行 EternallyConstant,輸出僅有 "Logging mode is on"。javap -c EternallyConstant 揭示真相:
Method void main(java.lang.String[])
0 getstatic #2 <Field java.io.PrintStream out>
3 ldc #3 <String "EternallyConstant beginning execution">
5 invokevirtual #4 <Method void println(java.lang.String)>
8 getstatic #5 <Field boolean log>
11 ifeq 22
14 getstatic #2 <Field java.io.PrintStream out>
17 ldc #6 <String "Logging mode is on">
19 invokevirtual #4 <Method void println(java.lang.String)>
22 return
完全沒有檢查 debug 欄位!因 final 編譯器知其運行期不變,優化移除分支。修改 final 欄位需重編譯所有引用類別。古老格言適用:「困惑時,全專案重編譯」。
javap 輸出位元碼助記符,第三方工具可直接還原原始碼。NMI's Java Code Viewer (NJCV) 便利支援 .class/.jar/.zip,適用於除錯、安全分析、學習優秀程式碼。
開啟 ExampleOfByteCode.class,選擇 Decompile,以內建編輯器檢視:
// Processed by NMI's Java Code Viewer 4.8.3
import java.io.PrintStream;
public class ExampleOfByteCode {
public ExampleOfByteCode() { }
public static void main(String args[]) {
System.out.println("Hello world");
}
public float add(float f, int n) { return f + (float)n; }
String concat(String str1, String str2) { return str1 + str2; }
void concat(StringBuffer str1, String str2) { str1.append(str2); }
}
支援直接從 jar/zip 提取類別,預設 .nmi 副檔名可改為 .java。結果與原始檔相差無幾。
了解位元碼助益 Java 程式開發。javap 讓檢視位元碼容易,第三方工具讓反編譯易如反掌。常用 javap 檢測程式碼、反編譯工具分析,對發現易忽略錯誤、提升效率、強化安全與效能價值不可估量。隨著 Java 技術發展、類別庫完善、跨平台優勢,Java 應用將日益廣泛。