常量池、运行时常量池、字符串常量池

1,385 阅读4分钟

学习JVM时候经常遇到这三个名词:常量池运行时常量池字符串常量池,接下来了解一下这三个到底有什么区别,也便于自己复习用到。

常量池

package com.example.demo;

/**
 * @author zinsanity
 * @date 2020-11-21 16:06
 * @desc
 */
public class Review {
    public static void main(String[] args) {
        String info = "hello world";
        int a = 666;
        final int b = 66;
        int c = 36728;
    }
}

此代码编译以后,使用javap -v Review.class命令查看字节码

Classfile /D:/idea_projects/demo/target/classes/com/example/demo/Review.class
  Last modified 2020-11-21; size 561 bytes
  MD5 checksum 09914fecad4a776e7ac86d1a6fd43baa
  Compiled from "Review.java"
public class com.example.demo.Review
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#26         // java/lang/Object."<init>":()V
   #2 = String             #27            // hello world
   #3 = Integer            36728
   #4 = Class              #28            // com/example/demo/Review
   #5 = Class              #29            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/example/demo/Review;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               info
  #18 = Utf8               Ljava/lang/String;
  #19 = Utf8               a
  #20 = Utf8               I
  #21 = Utf8               b
  #22 = Utf8               c
  #23 = Utf8               MethodParameters
  #24 = Utf8               SourceFile
  #25 = Utf8               Review.java
  #26 = NameAndType        #6:#7          // "<init>":()V
  #27 = Utf8               hello world
  #28 = Utf8               com/example/demo/Review
  #29 = Utf8               java/lang/Object
{
  public com.example.demo.Review();
    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 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/demo/Review;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=5, args_size=1
         0: ldc           #2                  // String hello world
         2: astore_1
         3: sipush        666
         6: istore_2
         7: bipush        66
         9: istore_3
        10: ldc           #3                  // int 36728
        12: istore        4
        14: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 7
        line 13: 10
        line 14: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  args   [Ljava/lang/String;
            3      12     1  info   Ljava/lang/String;
            7       8     2     a   I
           10       5     3     b   I
           14       1     4     c   I
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Review.java"

从字节码文件中可以看到Constant Pool也就是说,每个class文件,都有一个常量池。先来看这段字节码,这就是我们的main方法编译后的字节码,其中0: ldc #2 意思是从常量池中取出第2个位置的数据存入到astore_1当中。那么下面去常量池中看一下

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=5, args_size=1
         0: ldc           #2                  // String hello world
         2: astore_1
         3: sipush        666
         6: istore_2
         7: bipush        66
         9: istore_3
        10: ldc           #3                  // int 36728
        12: istore        4
        14: return

在字节码文件中,如下片段即表示常量池。#2位置对应的是String,然后还要去找#27位置,#27位置对应的是hello word。所以,0: ldc #2最终从常量池中取出来一个String类型的hello word字符串,存入到astore_1中。然后sipush就是将666存入到istore_2位置,bipush将66存入到istore_3位置,ldc命令又会去常量池找#3位置的数据存入。

Constant pool:
   #1 = Methodref          #5.#26         // java/lang/Object."<init>":()V
   ......
   #29
第一个疑问:为什么都是int类型的数据,用的指令却不同?按照JVM的规范,根据int值范围采用不同的指令将int数值入栈。


第二个疑问:为什么66666没有被放到常量池中?据了解,对于int类型,只有超过一定范围的int值,才会放到常量池中,
这也就解释了36728为何被放到了常量池中。

运行时常量池

在常量池中,可以看到都是用#1 #2 #3这些临时符号来表示。当运行某个程序时候,JVM会把所有的字节码文件加入到内存当中,在经过链接、验证后,将#1 #2 #3这些符号全部转换成内存中的实际地址,放入到运行时常量池运行。运行时常量池是放在方法区中的,全局只有一份,是一个被所有的class共享的区域。

字符串常量池

比如说

String a = "test1";
String b = "test2";

这两个字符串"test1"和"test2"在编译完成后,首先存放在常量池中。在程序运行时,加载进入内存以后,字符串就会加载进入到字符串常量池中。jdk1.7以后,字符串常量池位于堆中。