Java中的常量池

JVM中有:

Class文件常量池、运行时常量池、全局字符串常量池、基本类型包装类对象 常量池

Class文件常量池:
class文件是一组以字节为单位的二进制数据流,在java代码的编译期间,编写的java文件就被编译为.class文件格式的二进制数据存放在磁盘中,其中包括class文件常量池。
class文件中存在常量池(非运行时常量池),其在编译阶段就已经确定,jvm规范对class文件结构有着严格的规范,必须符合此规范的class文件才能被jvm认可和装载。
一下写个简单的类,并编译
package basics;

public class ConstanPoolTest {
    private int value = 1;
    public String s = "abc";
    public final static int f = 0x101;

    public void setValue(int v) {
        final int temp = 3;
        this.value = temp + v;
    }

    public int getValue() {
        return value;
    }
}
"D:Program FilesJavajdk1.8.0_202injavap.exe" -v basics.ConstanPoolTest
Classfile /D:/QFWorkspace/IdeaProjects/JavaReview/target/classes/basics/ConstanPoolTest.class
  Last modified 2019-8-4; size 627 bytes
  MD5 checksum bb29ca138910d931e8f2c777f6abb41b
  Compiled from "ConstanPoolTest.java"
public class basics.ConstanPoolTest
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#30         // basics/ConstanPoolTest.value:I
   #3 = String             #31            // abc
   #4 = Fieldref           #5.#32         // basics/ConstanPoolTest.s:Ljava/lang/String;
   #5 = Class              #33            // basics/ConstanPoolTest
   #6 = Class              #34            // java/lang/Object
   #7 = Utf8               value
   #8 = Utf8               I
   #9 = Utf8               s
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               f
  #12 = Utf8               ConstantValue
  #13 = Integer            257
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lbasics/ConstanPoolTest;
  #21 = Utf8               setValue
  #22 = Utf8               (I)V
  #23 = Utf8               v
  #24 = Utf8               temp
  #25 = Utf8               getValue
  #26 = Utf8               ()I
  #27 = Utf8               SourceFile
  #28 = Utf8               ConstanPoolTest.java
  #29 = NameAndType        #14:#15        // "<init>":()V
  #30 = NameAndType        #7:#8          // value:I
  #31 = Utf8               abc
  #32 = NameAndType        #9:#10         // s:Ljava/lang/String;
  #33 = Utf8               basics/ConstanPoolTest
  #34 = Utf8               java/lang/Object
{
  public java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC

  public static final int f;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 257

  public basics.ConstanPoolTest();
    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 value:I
         9: aload_0
        10: ldc           #3                  // String abc
        12: putfield      #4                  // Field s:Ljava/lang/String;
        15: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lbasics/ConstanPoolTest;

  public void setValue(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=2
         0: iconst_3
         1: istore_2
         2: aload_0
         3: iconst_3
         4: iload_1
         5: iadd
         6: putfield      #2                  // Field value:I
         9: return
      LineNumberTable:
        line 9: 0
        line 10: 2
        line 11: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lbasics/ConstanPoolTest;
            0      10     1     v   I
            2       8     2  temp   I

  public int getValue();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field value:I
         4: ireturn
      LineNumberTable:
        line 14: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lbasics/ConstanPoolTest;
}
SourceFile: "ConstanPoolTest.java"

Process finished with exit code 0
View Code

  

 可以看到该class文件的版本号、常量池、已经编译后的字节码。既然是常量池,那么其中存放的肯定是常量,那么什么是“常量”呢? class文件常量池主要存放两大常量:字面量符号引用

字面量: 字面量接近java语言层面的常量概念,主要包括:

  • 文本字符串,也就是我们经常申明的: public String s = "abc";中的"abc"
  • 用final修饰的成员变量,包括静态变量、实例变量和局部变量

注:上面说的存在于常量池的字面量,指的是数据的值,也就是abc和0x101(257),通过上面对常量池的观察可知这两个字面量是确实存在于常量池的。

而对于基本类型数据(甚至是方法中的局部变量),也就是上面的private int value = 1;常量池中只保留了他的的字段描述符I和字段的名称value,他们的字面量不会存在于常量池

2). 符号引用
符号引用主要设涉及编译原理方面的概念,包括下面三类常量:

  • 类和接口的全限定名,也就是Ljava/lang/String;这样,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用,像上面
#5 = Class              #33            // JavaBasicKnowledge/JavaBean
 #33 = Utf8               JavaBasicKnowledge/JavaBean
  • 字段的名称和描述符,字段也就是类或者接口中声明的变量,包括类级别变量和实例级的变量
#4 = Fieldref           #5.#32         // JavaBasicKnowledge/JavaBean.value:I
 #5 = Class              #33            // JavaBasicKnowledge/JavaBean
 #32 = NameAndType       #7:#8          // value:I

 #7 = Utf8               value
 #8 = Utf8               I
View Code


 //这两个是局部变量,值保留字段名称
 #23 = Utf8               v
 #24 = Utf8               temp

可以看到,对于方法中的局部变量名,class文件的常量池仅仅保存字段名。

  • 方法中的名称和描述符,也即参数类型+返回值
 #21 = Utf8               setValue
  #22 = Utf8               (I)V

  #25 = Utf8               getValue
  #26 = Utf8               ()I

运行时常量池

运行时常量池是方法区的一部分,所以也是全局贡献的,我们知道,jvm在执行某个类的时候,必须经过加载、链接(验证、准备、解析)、初始化,在第一步加载的时候需要完成:

  • 通过一个类的全限定名来获取此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个类对象,代表加载的这个类,这个对象是java.lang.Class,它作为方法区这个类的各种数据访问的入口。

类对象和普通对象是不同的,类对象是在类加载的时候完成的,是jvm创建的并且是单例的,作为这个类和外界交互的入口, 而普通的对象一般是在调用new之后创建。

上面的第二条,将class字节流代表的静态存储结构转化为方法区的运行时数据结构,其中就包含了class文件常量池进入运行时常量池的过程,这里需要强调一下不同的类共用一个运行时常量池,同时在进入运行时常量池的过程中,多个class文件中常量池中相同的字符串只会存在一份在运行时常量池,这也是一种优化。

运行时常量池的作用是存储java class文件常量池中的符号信息,运行时常量池中保存着一些class文件中描述的符号引用,同时在类的解析阶段还会将这些符号引用翻译出直接引用(直接指向实例对象的指针,内存地址),翻译出来的直接引用也是存储在运行时常量池中。

运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在编译时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()









原文地址:https://www.cnblogs.com/qingfengyiran-top1/p/11300654.html