JAVA-常量池

167 阅读8分钟

前言

在 JAVA-运行时数据区 那章中,出现了两个常量池,分别是 运行时常量池和字符窜长量池,其实还有个class文件常量池。以下内容只是综合参考文献以及自己的理解(可能还是错误的)

class 文件常量池

首先我们先分析java源文件以及它对应的class文件

public class App {
    public static void main(String[] args) throws InterruptedException {
        int age = 10;
        User user = new User();
        user.setAge(age);
        user.getAge();
    }

    public void test(){
        String a = new String("abc");
        String b = "abc";
    }
}

利用javap生成的字节码文件:

Classfile /D:/idea/jg/mytest/target/classes/org/example/App.class
 Last modified 2021-8-12; size 807 bytes
 MD5 checksum c0126011147c7f564677908c9b3bebcc
 Compiled from "App.java"
public class org.example.App
 minor version: 0
 major version: 51
 flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  #1 = Methodref          #10.#34        // java/lang/Object."<init>":()V
  #2 = Class              #35            // org/example/User
  #3 = Methodref          #2.#34         // org/example/User."<init>":()V
  #4 = Methodref          #2.#36         // org/example/User.setAge:(I)V
  #5 = Methodref          #2.#37         // org/example/User.getAge:()I
  #6 = Class              #38            // java/lang/String
  #7 = String             #39            // abc
  #8 = Methodref          #6.#40         // java/lang/String."<init>":(Ljava/lang/String;)V
  #9 = Class              #41            // org/example/App
 #10 = Class              #42            // java/lang/Object
 #11 = Utf8               <init>
 #12 = Utf8               ()V
 #13 = Utf8               Code
 #14 = Utf8               LineNumberTable
 #15 = Utf8               LocalVariableTable
 #16 = Utf8               this
 #17 = Utf8               Lorg/example/App;
 #18 = Utf8               main
 #19 = Utf8               ([Ljava/lang/String;)V
 #20 = Utf8               args
 #21 = Utf8               [Ljava/lang/String;
 #22 = Utf8               age
 #23 = Utf8               I
 #24 = Utf8               user
 #25 = Utf8               Lorg/example/User;
 #26 = Utf8               Exceptions
 #27 = Class              #43            // java/lang/InterruptedException
 #28 = Utf8               test
 #29 = Utf8               a
 #30 = Utf8               Ljava/lang/String;
 #31 = Utf8               b
 #32 = Utf8               SourceFile
 #33 = Utf8               App.java
 #34 = NameAndType        #11:#12        // "<init>":()V
 #35 = Utf8               org/example/User
 #36 = NameAndType        #44:#45        // setAge:(I)V
 #37 = NameAndType        #46:#47        // getAge:()I
 #38 = Utf8               java/lang/String
 #39 = Utf8               abc
 #40 = NameAndType        #11:#48        // "<init>":(Ljava/lang/String;)V
 #41 = Utf8               org/example/App
 #42 = Utf8               java/lang/Object
 #43 = Utf8               java/lang/InterruptedException
 #44 = Utf8               setAge
 #45 = Utf8               (I)V
 #46 = Utf8               getAge
 #47 = Utf8               ()I
 #48 = Utf8               (Ljava/lang/String;)V
{
 public org.example.App();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=1, locals=1, args_size=1
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
        4: return
     LineNumberTable:
       line 7: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0       5     0  this   Lorg/example/App;

 public static void main(java.lang.String[]) throws java.lang.InterruptedException;
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
     stack=2, locals=3, args_size=1
        0: bipush        10
        2: istore_1
        3: new           #2                  // class org/example/User
        6: dup
        7: invokespecial #3                  // Method org/example/User."<init>":()V
       10: astore_2
       11: aload_2
       12: iload_1
       13: invokevirtual #4                  // Method org/example/User.setAge:(I)V
       16: aload_2
       17: invokevirtual #5                  // Method org/example/User.getAge:()I
       20: pop
       21: return
     LineNumberTable:
       line 9: 0
       line 10: 3
       line 11: 11
       line 12: 16
       line 13: 21
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      22     0  args   [Ljava/lang/String;
           3      19     1   age   I
          11      11     2  user   Lorg/example/User;
   Exceptions:
     throws java.lang.InterruptedException

 public void test();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=3, locals=3, args_size=1
        0: new           #6                  // class java/lang/String
        3: dup
        4: ldc           #7                  // String abc
        6: invokespecial #8                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        9: astore_1
       10: ldc           #7                  // String abc
       12: astore_2
       13: return
     LineNumberTable:
       line 16: 0
       line 17: 10
       line 18: 13
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      14     0  this   Lorg/example/App;
          10       4     1     a   Ljava/lang/String;
          13       1     2     b   Ljava/lang/String;
}
SourceFile: "App.java"

SourceFile: "App.java" 利用javap生成的字节码文件:

Classfile /D:/idea/jg/mytest/target/classes/org/example/User.class
  Last modified 2021-8-11; size 461 bytes
  MD5 checksum f0f9061648604d27f0b37a1cd91d46ed
  Compiled from "User.java"
public class org.example.User
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // org/example/User.age:I
   #3 = Class              #22            // org/example/User
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               age
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lorg/example/User;
  #14 = Utf8               getAge
  #15 = Utf8               ()I
  #16 = Utf8               setAge
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               User.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // age:I
  #22 = Utf8               org/example/User
  #23 = Utf8               java/lang/Object
{
  public org.example.User();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
        line 6: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/User;

  public int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field age:I
         4: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/User;

  public void setAge(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 age:I
         5: return
      LineNumberTable:
        line 11: 0
        line 12: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lorg/example/User;
            0       6     1   age   I
}
SourceFile: "User.java"

这个就是class文件的内容,具体的文件结构内容可以参考
jvm指令手册
深入浅出JVM之Class文件结构
其中 Constant pool 就是class文件的常量池,常量池是class文件中最为重要的部分,因为其他的部分如方法、字段都会引用常量池的内容,也叫静态常量池,在编译阶段生成。主要存储字面量、常量、符号引用

字面量

前面说过,运行时常量池中主要保存的是字面量和符号引用,那么到底什么字面量?

在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。

以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。

字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=123这里的a为左值,123为右值。在这个例子中123就是字面量。

int a = 123;
String s = "hollis";复制ErrorOK!

上面的代码事例中,123和hollis都是字面量。

本文开头的HelloWorld代码中,Hollis就是一个字面量。

符号引用

常量池中,除了字面量以外,还有符号引用,那么到底什么是符号引用呢。

符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符

这也就可以印证前面的常量池中还包含一些com/hollis/HelloWorldmain([Ljava/lang/String;)V等常量的原因了

注意:

但并不是所有的字面量都被存到常量池中:
比如:

int i = 1; 

对应的jvm指令:

0: iconst_1 // 直接将数字1压栈
1: istore_1 // 弹出栈顶元素复值给 本地变量表下标为1的变量

为什么会出现这种情况呢?对于整数字面量来说,如果值在 -32768~32767 都会直接嵌入指令中,而不会保存在常量区。
但是如果使用 final 修饰变量,将其定义成类常量(注意不是在方法体内定义的局部常量),结果又有所不同,如下:

public class JgCrmWebApplication {
    final int a = 1;
    public static void main(String[] args) {
        int i = 1;
    }
}

则在生成的class文件中能找到 ConstantValue: int 1

运行时常量池

JVM 的类加载过程(加载、连接和初始化),如下图,将 .class 文件中的静态常量池转换为方法区的运行时常量池发生在“Loading”阶段,而符号引用替换为直接引用发生在 “Resolution”阶段

image.png

当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在虚拟机类加载过程时再进行详细讲解

字符窜常量池

由于字符串池是虚拟机层面的技术,所以在 String 的类定义中并没有类似 IntegerCache 这样的对象池,String 类中提及缓存/池的概念只有intern() 这个方法,而intern() 是一个native 的方法,那么说明它本身并不是由 Java 语言实现的,而是通过 jni (Java Native Interface)调用了其他语言(如C/C++)实现的一些外部方法(具体实现需要可以自己去看看),再字符窜常量池中,存的是各个字符窜的引用。

String.intern()

判断这个常量是否存在于常量池

  如果存在

   判断存在内容是引用还是常量,

    如果是引用,

     返回引用地址指向堆空间对象,

    如果是常量,

     直接返回常量池常量

  如果不存在,

   将当前对象引用复制到常量池,并且返回的是当前对象的引用

如代码:

String s1 =new String("he") + new String("llo");
String s2 = s1.intern();

在堆中的表示:

image.png

训练:

String a1 = "AA";
System.out.println(a1 == a1.intern()); 
String a2 = new String("B") + new String("B");
a2.intern();
String a3 = new String("B") + new String("B");
System.out.println(a2 == a3.intern());
System.out.println(a3 == a3.intern());

问题

位于字符窜长量池的对象是否会被垃圾回收?

参考文献

常量池
Java 8 中的常量池、字符串池、包装类对象池 jvm指令手册
深入浅出JVM之Class文件结构