java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java class常量池

Java 常量池详解之class文件常量池 和class运行时常量池

作者:new hilbert()

这篇文章主要介绍了Java 常量池详解之class文件常量池 和class运行时常量池,常量池主要存放两大类常量:字面量,符号引用,本文结合示例代码对java class常量池相关知识介绍的非常详细,需要的朋友可以参考下

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常量池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文