Java是一門"半編譯半解釋"型語言.通過使用jdk提供的javac編譯器可以將Java源碼編譯為Java虛擬機(Java Virtual Machine, JVM)可讀的字節碼(bytecode),即*.class文件.
學習字節碼可以使你更好的理解Java虛擬機的行為,甚至對學習其它基於Java虛擬機的語言(如:Scala,Clojure,Kotlin等)有很大的幫助
入門
實際上*.class文件並不是人類可讀的文件格式,我們可以使用JDK提供的反會匯編器javap來分析字節碼
Hello.java
class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
這可能是大家接觸的第一個Java程序,我們先使用=> javac Hello.java編譯得到Hello.class文件.然后使用javap -c -verbose將文件反匯編

常量池
其中Constant pool就是傳說中的常量池.
常量池可以看作是一個數組,#后面的數字代表數組的索引.
=后面是數組的值.
第一列代表這個常量的tag,第二列會根據tag的不同而不同.
以Class為例,tag為Class的常量結構體為
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
name_index 表示一個常量池中的有效索引,這個索引的tag必須是Utf8
在#5位置上的name_index為常量池中#21位置中的常量,實際上保存的就是這個類的類名.類名會使用正斜杠/代替.來表示類型的完整名稱
關於更多常量池的介紹可以查看
The Java Virtual Machine Specification 4.4
通過查看反匯編過的字節碼我們可以發現這個類有兩個方法,一個是無參的構造器
Hello(),另一個是main方法
descriptor
descriptor中描述這個參數和方法類型(返回值類型).其中()中代表的是這個方法的參數,后面跟的是這個方法的返回值類型,V代表void,即無返回值
下表列出了一些返回值符號對應的含義

需要注意的是引用類型名和常量池中類型的命名方式一致
比如main方法的參數描述符和類型描述符為([Ljava/lang/String;)V,代表這個方法接受一個String[]類型參數,並且無返回值.
flags
flags中描述了這個方法的訪問權限和基本屬性,下表列出flags描述符對應的含義

在上面我們定義的程序中並沒有顯示的定義構造器,這里的構造器屬於編譯器自動生成.但是jvm標准規定類型的初始化等與人工實現無關的方法可以不用加ACC_SYNTHETIC.注
code
code是方法的代碼部分
stack=2, locals=1, args_size=1,分別代表操作數棧的深度,局部變量表大小和方法參數個數.其中實例方法的第一個局部變量和參數是this.局部變量表中每個參數大小都是32位,所以long和double會占用局部變量表中兩個連續的位置
在構造器中,aload_0表示將第一個參數壓入棧中,即this.然后會使用invokespecial指令調用一個特殊的初始化方法java/lang/Object."<init>":()V
在main函數中,先使用getstatic指令獲取java/lang/System.out:Ljava/io/PrintStream的靜態域,再將它壓入棧中(java/io/PrintStream的實例).然后再使用ldc將常量池中的字符串指針(即"Hello World")壓入棧中,ldc指令表示將一個常量池中的對象壓入操作數棧中.
接着,使用invokevirtual調用java/io/PrintStream.println,invokevirtual指令用於調用實例的方法.最終調用return指令返回
