首頁 > 軟體

Java 常數池詳解之class檔案常數池 和class執行時常數池

2022-12-27 14:01:47

Java 常數池詳解(一)字串常數池

2.class檔案常數池(class constant pool)

產生時機:當java檔案被編譯成class檔案之後,就會生成class常數池,跟jvm 無關係

常數池主要存放兩大類常數:字面量;符號參照

字面量接近Java語言層面的常數概念,如文字字串、宣告為final的常數值等

(Integer、 Float 、Long、 Double、 String、 UTF-8)

符號參照包含三類參照:
1、 類和介面的全限定名 org.springframework…Bean
舉例:jvm/Hotspot/ConstantsTest

 #7 = Class              #41            // jvm/Hotspot/ConstantsTest
 #41 = Utf8               jvm/Hotspot/ConstantsTest

2、欄位的名稱和描述符
舉例: int b

 #4 = Fieldref           #7.#37         // jvm/Hotspot/ConstantsTest.b:I
 #7 = Class              #41            // jvm/Hotspot/ConstantsTest
 #37 = NameAndType        #17:#18        // b:I
 #41 = Utf8               jvm/Hotspot/ConstantsTest
 #17 = Utf8               b
 #18 = Utf8               I

3、方法的名稱和描述符
舉例:int getB()

 #9 = Methodref          #7.#42          // jvm/Hotspot/ConstantsTest.getB:()I
 #7 = Class              #41            // jvm/Hotspot/ConstantsTest
 #42 = NameAndType        #26:#27        // getB:()I 
 #41 = Utf8               jvm/Hotspot/ConstantsTest
 #26 = Utf8               getB
 #27 = Utf8               ()I

Class 檔案佈局 如下(跟方法區佈局息息相關的)

public class ConstantsTest {
    private static Integer a = 10;
    private int b;
    private String c = "cc";
    private static String d = "dd";

    public int getB() {
        return b;
    }

    public static int getA() {
        return a;
    }

    public static void main(String[] args) {
        ConstantsTest constantsTest = new ConstantsTest();
        constantsTest.getB();
        ConstantsTest.getA();
    }
}

//執行下面這個語句
javap -c -v -p ConstantsTest.class 得到

public class jvm.Hotspot.ConstantsTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#34        // java/lang/Object."<init>":()V
   #2 = String             #35            // cc
   #3 = Fieldref           #7.#36         // jvm/Hotspot/ConstantsTest.c:Ljava/lang/String;
   #4 = Fieldref           #7.#37         // jvm/Hotspot/ConstantsTest.b:I
   #5 = Fieldref           #7.#38         // jvm/Hotspot/ConstantsTest.a:Ljava/lang/Integer;
   #6 = Methodref          #39.#40        // java/lang/Integer.intValue:()I
   #7 = Class              #41            // jvm/Hotspot/ConstantsTest
   #8 = Methodref          #7.#34         // jvm/Hotspot/ConstantsTest."<init>":()V
   #9 = Methodref          #7.#42         // jvm/Hotspot/ConstantsTest.getB:()I
  #10 = Methodref          #7.#43         // jvm/Hotspot/ConstantsTest.getA:()I
  #11 = Methodref          #39.#44        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  #12 = String             #45            // dd
  #13 = Fieldref           #7.#46         // jvm/Hotspot/ConstantsTest.d:Ljava/lang/String;
  #14 = Class              #47            // java/lang/Object
  #15 = Utf8               a
  #16 = Utf8               Ljava/lang/Integer;
  #17 = Utf8               b
  #18 = Utf8               I
  #19 = Utf8               c
  #20 = Utf8               Ljava/lang/String;
  #21 = Utf8               d
  #22 = Utf8               <init>
  #23 = Utf8               ()V
  #24 = Utf8               Code
  #25 = Utf8               LineNumberTable
  #26 = Utf8               getB
  #27 = Utf8               ()I
  #28 = Utf8               getA
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Utf8               <clinit>
  #32 = Utf8               SourceFile
  #33 = Utf8               ConstantsTest.java
  #34 = NameAndType        #22:#23        // "<init>":()V
  #35 = Utf8               cc
  #36 = NameAndType        #19:#20        // c:Ljava/lang/String;
  #37 = NameAndType        #17:#18        // b:I
  #38 = NameAndType        #15:#16        // a:Ljava/lang/Integer;
  #39 = Class              #48            // java/lang/Integer
  #40 = NameAndType        #49:#27        // intValue:()I
  #41 = Utf8               jvm/Hotspot/ConstantsTest
  #42 = NameAndType        #26:#27        // getB:()I
  #43 = NameAndType        #28:#27        // getA:()I
  #44 = NameAndType        #50:#51        // valueOf:(I)Ljava/lang/Integer;
  #45 = Utf8               dd
  #46 = NameAndType        #21:#20        // d:Ljava/lang/String;
  #47 = Utf8               java/lang/Object
  #48 = Utf8               java/lang/Integer
  #49 = Utf8               intValue
  #50 = Utf8               valueOf
  #51 = Utf8               (I)Ljava/lang/Integer;
{
  private static java.lang.Integer a;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PRIVATE, ACC_STATIC

  private int b;
    descriptor: I
    flags: ACC_PRIVATE

  private java.lang.String c;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  private static java.lang.String d;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC

  public jvm.Hotspot.ConstantsTest();
    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: ldc           #2                  // String cc
         7: putfield      #3                  // Field c:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 10: 0
        line 13: 4

  public int getB();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #4                  // Field b:I
         4: ireturn
      LineNumberTable:
        line 17: 0

  public static int getA();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #5                  // Field a:Ljava/lang/Integer;
         3: invokevirtual #6                  // Method java/lang/Integer.intValue:()I
         6: ireturn
      LineNumberTable:
        line 21: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class jvm/Hotspot/ConstantsTest
         3: dup
         4: invokespecial #8                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #9                  // Method getB:()I
        12: pop
        13: invokestatic  #10                 // Method getA:()I
        16: pop
        17: return
      LineNumberTable:
        line 25: 0
        line 26: 8
        line 27: 13
        line 28: 17

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        10
         2: invokestatic  #11                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #5                  // Field a:Ljava/lang/Integer;
         8: ldc           #12                 // String dd
        10: putstatic     #13                 // Field d:Ljava/lang/String;
        13: return
      LineNumberTable:
        line 11: 0
        line 14: 8
}

3.class執行時常數池

Class執行時常數池其實是當類載入到記憶體中後,jvm就會將class常數池中的內容存放到執行時常數池中。 裡面放的也是符號參照和字面量,跟我們的class檔案常數池放的東西是一樣的

可以把Class檔案常數池看作靜態常數池(裡面是符號參照), 而執行時常數池是動態常數池(裡面有直接參照),他倆是同一個東西,只是狀態時機不同而已。

問題:Class 檔案資訊是否能跟方法區對應上來?

發現是能跟夠一一對應的上的。方法區其實就可以簡單看成執行狀態的Class檔案的佈局

1. 符號參照(脫離jvm 體系來講,就單純class檔案的符號參照而已)

符號參照以一組符號來描述所參照的目標,在編譯的時候一個每個java類都會被編譯成一個class檔案, 但在編譯的時候虛擬機器器並不知道所參照類的地址,多以就用符號參照來代替 ,而在這個解析階段就是為了把這個符號參照轉化成為真正的地址的階段

比如:class 檔案裡面有個方法呼叫getB()方法。 還沒到準備階段(虛擬機器器就會進入準備階段。在這個階段,虛擬機器器就會為這個類分配相應的記憶體空間,並設定預設初始值)。是不知道具體new 物件所在的地址的。class 檔案裡面只是符號參照,知道是用這個類的方法。

 public static void main(String[] args) {
        ConstantsTest constantsTest = new ConstantsTest();
        constantsTest.getB();
}
//對應的反編譯 程式碼其實 是
9: invokevirtual #9                  // Method getB:()I

但其實#9 是常數池裡面的 // jvm/Hotspot/ConstantsTest.getB:()I

2.直接參照

直接參照和虛擬機器器的佈局是相關的如果有了直接參照,那麼直接參照的目標一定被載入到了記憶體中。(有具體參照地址的指標,被參照的類、方法或者變數已經被載入到記憶體中)

3.靜態連結

當一個位元組碼檔案被裝載進 JVM 內部時,如果被呼叫的目標方法在編譯期可知,且執行期保持不變時,這種情況下將呼叫方法的符號參照轉換為直接參照的過程稱之為靜態連結

比如:呼叫靜態方法, 呼叫範例的私有構造器, 私有方法, 父類別方法,被final修飾的方法(其實就是不能被子類重寫的方法,能確定唯一性)

4.動態連結

如果被呼叫的方法在編譯期無法被確定下來,也就是說,只能夠在程式執行期將呼叫的方法的符號轉換為直接參照,由於這種參照轉換過程具備動態性,因此也被稱之為動態連結。

比如:(B b = new B2() ) 父類別宣告,子類實現的情況,方法被子類重寫了。

案例分析

有一個父類別B 和子類B2 ,子類B2實現了B的getA 方法

public class B {
    private int a = 2;

    public int getA() {
        return a;
    }

    public static void print() {
        System.out.println("aaa");
    }

    public final void b() {

    }

    private void c() {
        System.out.println("c");
    }
}

//只能重寫getA方法
public class B2 extends B {
    @Override
    public int getA() {
        return 2;
}   

此時有個執行緒呼叫了宣告為B ,但是實現為B2的方法

public static void main(String[] args) {
    B b2 = new B2();
    b2.getA();
    b2.b();
}

class檔案編譯結果如下:

0: new           #4                  // class jvm/Hotspot/B2
3: dup
4: invokespecial #5                  // Method jvm/Hotspot/B2."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #2                  // Method jvm/Hotspot/B.getA:()I
12: pop
13: aload_1
14: invokevirtual #6                  // Method jvm/Hotspot/B.b:()V
17: return

此時這個 b2.getA() 他是動態連結,他只有在執行期間才知道用了具體哪個類,因為他這個方法可以被重寫了,他可以是B類的getA方法,也可以是B2的get方法。此時這個 b2.b() 他是靜態連結,他在編譯期就確定是B類的b方法,是可以直接參照的

問題 :動態連結跟什麼有關係?

動態連結在棧幀裡面,不會在停留在方法區裡面,是跟執行緒有關係的

public class A {
    int a = 1;
    public int getA() {
        return a;
    }
}

public class A2 extends A {
    @Override
    public int getA() {
        return 10;
    }
}

public class B {
    public int getB(A a) {
        return a.getA();
    }
}

public class B2 extends B {
    @Override
    public int getB(A a) {
        return a.getA()+1;
    }
}

執行緒呼叫

public static void main(String[] args) {
        B b2 = new B2();
        A a2 = new A2();
        b2.getB(a2);
}

//java反編譯之後得到
0: new           #2                  // class jvm/Hotspot/B2
3: dup
4: invokespecial #3                  // Method jvm/Hotspot/B2."<init>":()V
7: astore_1
8: new           #4                  // class jvm/Hotspot/A2
11: dup
12: invokespecial #5                  // Method jvm/Hotspot/A2."<init>":()V
15: astore_2
16: aload_1
17: aload_2
18: invokevirtual #6          // Method jvm/Hotspot/B.getB:(Ljvm/Hotspot/A;)I
21: pop
22: return

雖然我們初始化的是B2的類,但是符號參照是B,是根據宣告量B來的
然後動態連結會幫助我們在用b2.getB方法的時候,幫我們指向B2 而不是一開始寫的B。

到此這篇關於Java 常數池詳解之class檔案常數池 和class執行時常數池的文章就介紹到這了,更多相關Java class常數池內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com