01 字節碼初體驗 —— 從 Hello World 說起

Hello.java

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}

javac

把源文件編譯成 JVM 可識別的 class 文件

javac Hello.java

javac Hello.java

xxd 查看 class 文件

xxd 是 Linux 的命令,所以把 javac Hello.java 生成的 Hello.class 文件拷貝到虛擬機中

打開 Linux 終端,使用 xxd 命令,以 16 進制方式查看 class 文件

xxd Hello.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

javap

其中 -c、-v、-l、-p、-s 最常用

  • -c:對類進行反編譯
  • -p:顯示 private 方法和字段(不加 -p,默認只顯示 public、protected 和默認級別方法和字段)
  • -v:輸出更多詳細信息,如棧大小、方法參數個數等
  • -s:輸出簽名的類型描述符

javap -c

javap -c Hello

對類進行反編譯
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

輸出更多詳細信息,如棧大小、方法參數個數等
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

輸出簽名的類型描述符
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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章