searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

JVM中几种常量池的分析

2023-05-30 01:12:57
2
0

在JVM的内存模型中,我们经常听到常量池的概念,我一开始时认为常量池就是一块内存区域,所有常量存放于其中,最近在学习JVM,看到好几种常量池的说法,把自己都搞糊涂了。花了一天时间,查询各种资料,所幸有所顿悟,决定记录下来,方便查阅。

注意本文以JVM8-HotSpot为基准。

 

1. 相关知识科普

(1)什么是常量

用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态变量(类变量)、实例变量和局部变量,分别表示三种类型的常量。

(2)为什么使用常量池,即常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串字面量放到一个常量池中。

  • 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间;

  • 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等,new出来的不行。

(3)==的含义

  • 基本数据类型之间使用==,比较的是他们的数值;

  • 引用之间应用==,比较的是他们在内存中的存放地址。

2. class文件常量池(class constant pool)

class文件是一组以字节为单位(因此也被称为字节码文件)的二进制数据流,我们编写的.java文件在编译后生成.class文件,其中就包括class文件常量池。为了方便说明,我们写个简单的类。
public class App {
    private String value = "Hello";
    public int id = 110;
    public final static int CONSTANT = 0x110;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        int tmp = id + 1;
        this.id = tmp;
    }
}

使用javac编译生成App.class,再使用javap -v App.class 查看编译后的内容,这里主要列出常量池的部分:

Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."<init>":()V
   #2 = String             #30            // Hello
   #3 = Fieldref           #5.#31         // com/taylor/test/jvm/App.value:Ljava/lang/String;
   #4 = Fieldref           #5.#32         // com/taylor/test/jvm/App.id:I
   #5 = Class              #33            // com/taylor/test/jvm/App
   #6 = Class              #34            // java/lang/Object
   #7 = Utf8               value
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               id
  #10 = Utf8               I
  #11 = Utf8               CONSTANT
  #12 = Utf8               ConstantValue
  #13 = Integer            272
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/taylor/test/jvm/App;
  #21 = Utf8               getId
  #22 = Utf8               ()I
  #23 = Utf8               setId
  #24 = Utf8               (I)V
  #25 = Utf8               tmp
  #26 = Utf8               MethodParameters
  #27 = Utf8               SourceFile
  #28 = Utf8               App.java
  #29 = NameAndType        #14:#15        // "<init>":()V
  #30 = Utf8               Hello
  #31 = NameAndType        #7:#8          // value:Ljava/lang/String;
  #32 = NameAndType        #9:#10         // id:I
  #33 = Utf8               com/taylor/test/jvm/App
  #34 = Utf8               java/lang/Object

class文件常量池主要存放两大常量:字面量(Literal)和符号引用(Symbolic References)。

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

  • 文本字符串,也就是我们经常声明的:public String value = "Hello";中的"Hello"

    #2 = String             #29            // Hello
    #30 = Utf8               Hello
  • 用final修饰的静态变量

    #13 = Integer            272

    强调一下,字面量指的是数据的值,即”Hello“和0x110(272)。通过上面对常量池的观察,这两个字面值确实存在于常量池中。而对于实例变量,即上面的public int id = 110,常量池只保留了他们的字段描述符I和字段的名称id,他们的字面量不会存在于常量池中。小朋友,你是不是有很多问号?110去哪里了?答案是,他们在对应的字节码方法里:

    public com.taylor.test.jvm.App();
        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 Hello
             7: putfield      #3                  // Field value:Ljava/lang/String;
            10: aload_0
            11: bipush        110
            13: putfield      #4                  // Field id:I
            16: return​
    第11行对应的 bipush 110

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

    • 类和接口的全限定名,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用

       #5 = Class              #27            // com/taylor/test/jvm/App
       #33 = Utf8               com/taylor/test/jvm/App
    • 字段的名称和描述符(字段类型),字段也就是类或者接口中声明的变量,包括类变量和实例变量

0条评论
作者已关闭评论
z****n
3文章数
0粉丝数
z****n
3 文章 | 0 粉丝
z****n
3文章数
0粉丝数
z****n
3 文章 | 0 粉丝
原创

JVM中几种常量池的分析

2023-05-30 01:12:57
2
0

在JVM的内存模型中,我们经常听到常量池的概念,我一开始时认为常量池就是一块内存区域,所有常量存放于其中,最近在学习JVM,看到好几种常量池的说法,把自己都搞糊涂了。花了一天时间,查询各种资料,所幸有所顿悟,决定记录下来,方便查阅。

注意本文以JVM8-HotSpot为基准。

 

1. 相关知识科普

(1)什么是常量

用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态变量(类变量)、实例变量和局部变量,分别表示三种类型的常量。

(2)为什么使用常量池,即常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把所有的字符串字面量放到一个常量池中。

  • 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间;

  • 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等,new出来的不行。

(3)==的含义

  • 基本数据类型之间使用==,比较的是他们的数值;

  • 引用之间应用==,比较的是他们在内存中的存放地址。

2. class文件常量池(class constant pool)

class文件是一组以字节为单位(因此也被称为字节码文件)的二进制数据流,我们编写的.java文件在编译后生成.class文件,其中就包括class文件常量池。为了方便说明,我们写个简单的类。
public class App {
    private String value = "Hello";
    public int id = 110;
    public final static int CONSTANT = 0x110;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        int tmp = id + 1;
        this.id = tmp;
    }
}

使用javac编译生成App.class,再使用javap -v App.class 查看编译后的内容,这里主要列出常量池的部分:

Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."<init>":()V
   #2 = String             #30            // Hello
   #3 = Fieldref           #5.#31         // com/taylor/test/jvm/App.value:Ljava/lang/String;
   #4 = Fieldref           #5.#32         // com/taylor/test/jvm/App.id:I
   #5 = Class              #33            // com/taylor/test/jvm/App
   #6 = Class              #34            // java/lang/Object
   #7 = Utf8               value
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               id
  #10 = Utf8               I
  #11 = Utf8               CONSTANT
  #12 = Utf8               ConstantValue
  #13 = Integer            272
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/taylor/test/jvm/App;
  #21 = Utf8               getId
  #22 = Utf8               ()I
  #23 = Utf8               setId
  #24 = Utf8               (I)V
  #25 = Utf8               tmp
  #26 = Utf8               MethodParameters
  #27 = Utf8               SourceFile
  #28 = Utf8               App.java
  #29 = NameAndType        #14:#15        // "<init>":()V
  #30 = Utf8               Hello
  #31 = NameAndType        #7:#8          // value:Ljava/lang/String;
  #32 = NameAndType        #9:#10         // id:I
  #33 = Utf8               com/taylor/test/jvm/App
  #34 = Utf8               java/lang/Object

class文件常量池主要存放两大常量:字面量(Literal)和符号引用(Symbolic References)。

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

  • 文本字符串,也就是我们经常声明的:public String value = "Hello";中的"Hello"

    #2 = String             #29            // Hello
    #30 = Utf8               Hello
  • 用final修饰的静态变量

    #13 = Integer            272

    强调一下,字面量指的是数据的值,即”Hello“和0x110(272)。通过上面对常量池的观察,这两个字面值确实存在于常量池中。而对于实例变量,即上面的public int id = 110,常量池只保留了他们的字段描述符I和字段的名称id,他们的字面量不会存在于常量池中。小朋友,你是不是有很多问号?110去哪里了?答案是,他们在对应的字节码方法里:

    public com.taylor.test.jvm.App();
        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 Hello
             7: putfield      #3                  // Field value:Ljava/lang/String;
            10: aload_0
            11: bipush        110
            13: putfield      #4                  // Field id:I
            16: return​
    第11行对应的 bipush 110

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

    • 类和接口的全限定名,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用

       #5 = Class              #27            // com/taylor/test/jvm/App
       #33 = Utf8               com/taylor/test/jvm/App
    • 字段的名称和描述符(字段类型),字段也就是类或者接口中声明的变量,包括类变量和实例变量

文章来自个人专栏
大数据jvm
1 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0