JVM(三):深入分析Java字節碼-上

JVM(三):深入分析Java字節碼-上

字節碼文章分爲上下兩篇,上篇也就是本文主要講述class文件存在的意義,以及其帶來的益處。並分析其內在構成之一 ———字節碼,而下篇則從指令集方面着手,講解指令集都有哪些,以及其各自代表的含義。最後總結一下Class文件存在的必然性。

意義

前面說過 Java 虛擬機擁有平臺無關性,但其實現在語言無關性在 JVM 和更加的體現了出來。表現就是目前越來越多的語言可以在 JVM 上運行,而這背後的邏輯,就是這些語言都會被編譯爲 Class 文件,然後在JVM上 運行。

In the future,we will consider bounded extensions to the java virtual machine to provide better support for other languages.

上面這段是 JVM 的相關人員提出的願景,因此我們也對 Java 語言的發展更加的看好。

那麼在下面的文章中,我們就來探討 Class 文件的組成部分,瞭解其內部是如何組織的。

Class類文件

首先我們編寫一個原始的Java源代碼:

public class TestForEach extends Thread{
private static int ccc = 1;
public static void main(String[] args) {

   int a = 1;
   int b = 2;
   int c = a + b;
}

這裏我們使用JDk提供的工具對代碼進行編譯,得到下面這個二進制流。

    cafe babe 0000 0034 001d 0a00 0600 0f09
    0010 0011 0800 120a 0013 0014 0700 1507
    0016 0100 063c 696e 6974 3e01 0003 2829
    5601 0004 436f 6465 0100 0f4c 696e 654e
    756d 6265 7254 6162 6c65 0100 046d 6169
    6e01 0016 285b 4c6a 6176 612f 6c61 6e67
    2f53 7472 696e 673b 2956 0100 0a53 6f75
    .........

對我們來說,所需要分析的就是這個文件。該二進制流的前4個字節cafe babe,其被稱爲魔數。它代表了這是一個.class類型的文件,緊接着的第五第六個字節爲次版本號,第七第八個字節爲主版本號,而我們編譯的這個版本是在 JDK1.8 下。再緊接着就是常量池,訪問標示等信息,因爲這些信息計算十分的繁瑣麻煩,在這裏就不展開來計算了,而官方也細心地提供了javap 工具進行反編譯來查看其組織形式。

字節碼文件

首先我們採用javap工具進行反編譯javap -verbose TestForEach,得到如下文件

      Last modified 2019-5-22; size 461 bytes
      MD5 checksum 2602dfd883d5d5e417e26ce2d42b916d
      Compiled from "TestForEach.java"
    public class TestForEach extends java.lang.Thread
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Thread."<init>":()V
       #2 = Fieldref           #5.#23         // TestForEach.d:I
       #3 = Fieldref           #5.#24         // TestForEach.dfinal:I
       #4 = Fieldref           #5.#25         // TestForEach.ccc:I
       #5 = Class              #26            // TestForEach
       #6 = Class              #27            // java/lang/Thread
       #7 = Utf8               ccc
       #8 = Utf8               I
       #9 = Utf8               d
      #10 = Utf8               dfinal
      #11 = Utf8               ConstantValue
      #12 = Integer            2222
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               main
      #18 = Utf8               ([Ljava/lang/String;)V
      #19 = Utf8               <clinit>
      #20 = Utf8               SourceFile
      #21 = Utf8               TestForEach.java
      #22 = NameAndType        #13:#14        // "<init>":()V
      #23 = NameAndType        #9:#8          // d:I
      #24 = NameAndType        #10:#8         // dfinal:I
      #25 = NameAndType        #7:#8          // ccc:I
      #26 = Utf8               TestForEach
      #27 = Utf8               java/lang/Thread
    {
      public TestForEach();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Thread."<init>":()V
             4: aload_0
             5: iconst_1
             6: putfield      #2                  // Field d:I
             9: aload_0
            10: sipush        2222
            13: putfield      #3                  // Field dfinal:I
            16: return
          LineNumberTable:
            line 11: 0
            line 17: 4
            line 18: 9
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=4, args_size=1
             0: iconst_2
             1: istore_2
             2: iconst_1
             3: iload_2
             4: iadd
             5: istore_3
             6: return
          LineNumberTable:
            line 22: 0
            line 23: 2
            line 111: 6
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: iconst_1
             1: putstatic     #4                  // Field ccc:I
             4: return
          LineNumberTable:
            line 16: 0
    }
    SourceFile: "TestForEach.java"

文件頭

我們從頭開始看起,首先是

      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER

這裏分別爲Class文件的次版本和主版本號以及訪問標示,版本號前面已經說過了,這裏就不多贅述了,至於訪問標誌,其代表了一些類或接口的訪問信息,如是否是public類型的,有沒有被聲明成final等等。

常量池

接着到了 ConstantPool常量池,其可以簡單理解爲Class文件中的資源倉庫,許多部分都與其關聯。

其中存放了字面量和符號引用兩種類常量。字面量可以理解爲文本字符串和聲明爲final的常量值等。符號引號則包含以下內容:

  • 類和接口的全限定名;
  • 字段的名稱和描述符;
  • 方法的名稱和描述符。

上面兩種類型在常量池中都是以表的形式來存儲,其具體含義如下圖所示:
常量池的項目類型

常量池字節碼錶示

針對常量池內每個表的含義和我們得到的class文件,在這作者分析幾個看一下:

  • Methodref:方法的符號引用,具體內容跳到6和22行,但從後面的註釋可以看到是父類Thread的構造方法
  • Fieldref:字段的符號引用,具體內容在5,23行,可以看到是在TestForEach類中定義了int類型的屬性d
  • Class:類或接口的符號引用,從中可以看出該文件是TestForEach類,繼承自Thread
  • Utf8:表明其是一個字符串,在文件中字段描述ccc,d,dfinal,全限定名java/lang/Thread等,方法描述main,()V等都屬於這一類
  • NameAndType:字段或方法的符號引用,與上面不同的是,其沒有聲明是哪一個類的
  • .....

更多的這裏就不詳細展開了,但常量池中還有一些上面沒有提到的內容,在這裏我們細說一下.

屬性表集合

屬性表集合用於描述某些場景中專有的信息.針對上文出現的屬性表,我們這裏詳細說下.

  • ConstantValue:final關鍵字定義的常量值;
  • Code:Java代碼編譯而成字節碼指令,具體指令含義在下篇中細說;
  • LineNumberTable:Java源碼行號和字節碼指令的對應關係;
  • SourceFile:源文件的名稱
  • .........

總結

本文講了 Class 文件在 Java 達成平臺無關性和語言無關性起到的重要作用,並敘述了Class文件的重要組成部分----文件標識,常量池,屬性集合等。此外還對文件標識和常量池的內容進行了具體的展開描述。

下篇文章則從 JVM 支持的指令集內容開始介紹,並以本文的 Class 文件的指令集集合爲例,具體描述一下指令集的內容。

iceWang公衆號

文章在公衆號"iceWang"第一手更新,有興趣的朋友可以關注公衆號,第一時間看到筆者分享的各項知識點,謝謝!筆芯。

本系列文章主要借鑑自《深入分析JavaWeb技術內幕》和《深入理解Java虛擬機-JVM高級特性與最佳實踐》。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章