1. 程式人生 > >Java字節碼常量池深度剖析與字節碼整體結構分解

Java字節碼常量池深度剖析與字節碼整體結構分解

png java字節碼 技術分享 type local 類型 ctu jsp ()

常量池深度剖析:

在上一次【https://www.cnblogs.com/webor2006/p/9416831.html】中已經將常量池分析到了2/3了,接著把剩下的分析完,先回顧一下我們編譯的源文件為:

技術分享圖片

然後用javap -verbose查看一下編譯字節碼的信息,其中字符串相關的如下:

技術分享圖片

而對應用Hex Fiend來查看字符碼的二進制文件的位置如下:

技術分享圖片

另外在繼續分析之前再來回顧下常量的對應表,如下:

技術分享圖片

好下面開始,先來讀一個字節來看一下是什麽類型的常量:

技術分享圖片

查表可以看到是屬於這個常量:

技術分享圖片

接著2個字節表示字符串的長度,所以往下數二個字節:

技術分享圖片

長度為4,則下往下數4個字節則為常量的內容:

技術分享圖片

用javap -verbose來確認一下是否也是它:

技術分享圖片

接下來繼續讀一個字節:

技術分享圖片

又是同樣的常量類型,所以直接再讀二個字節來看一下字符串的長度是多少:

技術分享圖片

長度為3,則往後再數3個字節:

技術分享圖片

看一下javap -verbose:

技術分享圖片

實際上"getA()I"就可以確認其方法名為getA,無參,並且返回值為整型,就可以完全的對應的源程序中的方法了。

接下來繼續往下,讀一個字節:

技術分享圖片

同樣的類型,不多說,直接往下再看兩個字節來決定字符串的長度:

技術分享圖片

占四個字節,於是乎往後再數四個字節:

技術分享圖片

對一下javap -verbose:

技術分享圖片

繼續往下,讀一個字節:

技術分享圖片

再數2個字節:

技術分享圖片

往下數四個字節:

技術分享圖片

對應於javap -verbose:

技術分享圖片

而同樣的“setA(I)V”,表示方法名為setA,方法的參數為整型,無返回值,這樣又可以定位到具體的唯一的方法了。

繼續往下:

技術分享圖片

技術分享圖片

往下數10個字節:

技術分享圖片

對應javap -verbose:

技術分享圖片

代表源文件,再往下讀:

技術分享圖片

技術分享圖片

長度為12,往下數12個字節:

技術分享圖片

而這兩個信息描述了當前字節碼文件是由哪個源文件編譯出來的,所以這也是為啥在執行javap命令時有如下一個信息:

技術分享圖片

繼續往下走:

技術分享圖片

此時不再是01類型的常量了,而是12,所以具體它代表什麽類型還得查表,如下:

技術分享圖片

其第二項表示名稱的索引,而第三項為描述的索引,所以往下讀4個字節:

技術分享圖片

看一下javap -verbose所顯示的:

技術分享圖片

其中方法名稱為<init>表示是構造方法,而()V表示該構造方法不帶參數沒的返回值,也就是默認構造方法。

接下來繼續:

技術分享圖片

又是同樣的常量,所以直接往後數四個字節:

技術分享圖片

對應javap -verbose:

技術分享圖片

這個信息表示成員變量a,如下:

技術分享圖片

繼續往下看:

技術分享圖片

字符串常量,對它的分析已經了如指掌了,往後數兩個字節來看下字符串的長度:

技術分享圖片

長度為24,則往後數24個字節:

技術分享圖片

對應javap -verbose:
技術分享圖片

表示類的全局限定名,註意反應到字節碼文件來說全局限定名都是以“/”分隔的,而不像我們看到的包名那樣以“.”分隔的,繼續往下:

技術分享圖片

再往下數兩個字節:

技術分享圖片

16,則往下數16個字節:

技術分享圖片

對應javap -verbose:

技術分享圖片

表示當前類的父類的完全限制名,到此,常量池就全部分析完了~所以總結一下,對於一個類常量池的大小是不定的,那JVM如何在字體碼文件中來知道常量池在哪結束呢?首先字符碼能知道常量池的總大小,如下:

技術分享圖片

為24個,但是由於第一個為備用的,所以總常量池的大小為23,而每個常量第一個字節都是什麽類型的常量,然後不同的常量其往下讀幾個字節都是確定的,所以這樣就可以知道讀到哪常量池就結束了。

字節碼整體結構分解:

上面已經將整個常量池都已經分析完了,那之後還有那麽多字節:

技術分享圖片

對應javap -verbose:

Classfile /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes/com/jvm/bytecode/MyTest1.class
  Last modified Aug 10, 2018; size 479 bytes
  MD5 checksum 4616561f95c24d6b04ea48a360437b8d
  Compiled from "MyTest1.java"
public class com.jvm.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // com/jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/jvm/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/jvm/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               com/jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public com.jvm.bytecode.MyTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 3: 0
        line 4: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/jvm/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/jvm/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 11: 0
        line 12: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/jvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

目前還分析不了,因為還缺少知識理論,所以先來補一補知識,先來對字節碼的整體結構有一個了解,先來看張圖:

技術分享圖片

目前已經學習了前三個結構,如下:

技術分享圖片

接著來了解新的字節結構,接下來是“Access Flags”,表示訪問修飾符:

技術分享圖片

如:public、public static、public abstract、private、protected等。

接著往下表示當前類的名字,如下:

技術分享圖片

再往下表示父類的名字:

技術分享圖片

接下來表示接口相關的信息:

技術分享圖片

其中可以發現一個細節:

技術分享圖片

父類的字節數是確定的,而接口是不確實的,這也跟java的單繼承多實現的特性吻和。

繼續往下則是字段相關的信息:

技術分享圖片

接下來由是類的方法相關的一些信息:

技術分享圖片

這個就比較復雜了,因為方法裏面有執行代碼,在未來會學習到。

最後則表示當前類的一些附加的屬性:

技術分享圖片

因為JVM在編譯時會增加一些特定的一些屬性信息。

Java字節碼常量池深度剖析與字節碼整體結構分解