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

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

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

意義

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

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

上面這段是 JVM 的相關人員提出的願景,所以咱們也對 Java 語言的發展更加的看好。工具

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

Class類文件

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

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提供的工具對代碼進行編譯,獲得下面這個二進制流。blog

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"

文件頭

咱們從頭開始看起,首先是ip

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高級特性與最佳實踐》。

相關文章
相關標籤/搜索