Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
javac
把源文件編譯成 JVM 可識別的 class 文件
javac Hello.java
xxd 查看 class 文件
xxd 是 Linux 的命令,所以把 javac Hello.java 生成的 Hello.class 文件拷貝到虛擬機中
打開 Linux 終端,使用 xxd 命令,以 16 進制方式查看 class 文件
xxd Hello.class
class 文件的頭四個字節稱爲魔數(Magic Number),由上圖可知,class 的魔數爲 0xCAFEBABE,魔數是用來進行文件類型區分的,PDF 文件的魔數是 %PDF-(16 進制 0x255044462D),png 文件的魔數是 .png(0x89504E47),魔數 0xCAFEBABE 是 JVM 識別 .class 文件的標誌,虛擬機在加載類文件之前會先檢查這四個字節,如果不是 0xCAFEBABE 則拒絕加載
javap
直接分析 .class 類文件(二進制塊)比較難,JDK 提供的 javap 可用來分析類文件
查看 javap 的參數選項
javap
其中 -c、-v、-l、-p、-s 最常用
- -c:對類進行反編譯
- -p:顯示 private 方法和字段(不加 -p,默認只顯示 public、protected 和默認級別方法和字段)
- -v:輸出更多詳細信息,如棧大小、方法參數個數等
- -s:輸出簽名的類型描述符
javap -c
javap -c Hello
對類進行反編譯
把 -c 參數打印結果複製出來,如下
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
默認構造器部分
3 ~ 7 行:可以看到雖然代碼中沒有明確寫出 Hello 類的構造器函數,編譯器會自動加上一個默認構造器函數
5 行:aload_0(aload_x 格式操作碼中的一個,x 表述正在被訪問的局部變量數組的位置)用於把對象引用加載到操作數棧。這裏的 0 代表 this(非靜態的函數都有第一個默認參數,就是 this),這裏的 aload_0 意思就是把 this 入棧
6 行:invokespecial #1,incokespecial 指令用於調用實例初始化方法、私有方法、父類方法,#1 指常量池中的第 1 個,這裏是方法引用 Method java/lang/Object.""?)V,即構造器函數引用
7 行:return 屬於 ireturn、lreturn、freturn、dreturn、areturn 和 return 操作碼組中的一員,i 表示 int,l 表示 long,f 表示 float,d 表示 double,a 表示對象引用,沒有前綴的 return 表示返回 void
main() 函數部分
11 行:getstatic #2,getstatic 用於獲取指定類的靜態域,並將其值壓入棧頂,#2 代表常量池中的第 2 個, 這裏調用 Field java/lang/System.out:Ljava/io/PrintStream;,即 java.lang.System 類的靜態變量 out(類型是 java.io.PrintStream)
12 行:ldc #3,ldc 用於將常量從運行時常量池壓棧到操作數棧,#3 代表常量池中的第 3 個,這裏指 String 字符串 Hello, World
13 行:invokevirtual #4,invokevirtual 指令用於調用一個對象的實例方法,#4 代表常量池中的第 4 個,這裏是方法引用 Method java/io/PrintStream.println:(Ljava/lang/String;)V,即 PrintStream.println(String) 函數引用,並把棧頂兩個元素出棧
注意:11 行對應的棧結構只有一個元素,棧頂是 System.out,12 行對應的棧結構有兩個元素,字符串 Hello World 並位於棧頂,然後是 System.out,13 行調用完 println() 後,棧中的兩個元素都出站棧(方法執行完了)
javap -v
javap -v Hello
輸出更多詳細信息,如棧大小、方法參數個數等
把 -v 參數打印結果複製出來,如下
Classfile /D:/eclipse/workspace/Hello.class
Last modified 2019-6-2; size 416 bytes
MD5 checksum 9259a77d66ab0e31d6f4f9608a86caf5
Compiled from "Hello.java"
public class Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello, World
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Hello
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Hello.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello, World
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Hello
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "Hello.java"
- 43 行:Hello 的構造器函數沒有參數,但對於非靜態函數來說,this 會作爲函數的第一個參數,參數個數爲 1
- 54 行:main() 函數時靜態函數,不需要 this 對象,函數參數是 String[] args,參數個數爲 1
javap -s
javap -s
輸出簽名的類型描述符
把 -s 參數打印結果複製出來,如下
Compiled from "Hello.java"
public class Hello {
public Hello();
descriptor: ()V
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
}
- 默認構造方法 Hello() 函數方法簽名是 ()V
- main() 函數方法簽名是 ([Ljava/lang/String;)V