六、jvm中常量池

目录

静态常量池

静态常量池类型

什么是字面量

什么是符号引用

运行时常量池

字符串常量池

intern方法

底层原理

基础包装类型的常量池

整型常量池


静态常量池

静态常量池,主要指的是在编译期被存入.class文件中的常量池。这部分常量池主要包含直接量(如文本字符串、已知的数值和符号引用)等编译期就能确定的常量。  静态常量池是Java类文件结构的一部分,它主要用于存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

静态常量池类型

主要类型包含:

1. **CONSTANT_Utf8_info**:UTF-8编码的字符串。

2. **CONSTANT_Integer_info**:整型字面量。  

3. **CONSTANT_Float_info**:浮点型字面量。  

4. **CONSTANT_Long_info**:长整型字面量。

5. **CONSTANT_Double_info**:双精度浮点型字面量。

6. **CONSTANT_String_info**:字符串类型的字面量。

7. **CONSTANT_Class_info**:类或接口的符号引用。
8. **CONSTANT_Fieldref_info**:字段的符号引用。

9. **CONSTANT_Methodref_info**:类中方法的符号引用。  

10. **CONSTANT_InterfaceMethodref_info**:接口中方法的符号引用。  

11. **CONSTANT_NameAndType_info**:字段或方法的名称和描述符。  

12. **CONSTANT_MethodHandle_info**:表示方法句柄。  

13. **CONSTANT_MethodType_info**:表示方法类型。  

14. **CONSTANT_InvokeDynamic_info**:表示引导方法调用的动态链接。  

15. **CONSTANT_Module_info** 和 **CONSTANT_Package_info**:这两种类型是在Java 9中引入的,用于支持模块系统。 

什么是字面量

在java文件中定义的数字或者字符串都成为字面量

int a = 1;
int b = 2;
String c = "hello"

 上述代码中,1、2、“hello” 都是字面量。对应着常量池中Integer_info和String_info。

什么是符号引用

符号引用主要包含以下几种:

1、类和节点的符号引用

2、字段符号引用

3、方法符号引用

int a = 1;
String b = "hello";

public void sayHello(){}

上述代码中a、b、 sayHello都是符号应用。

运行时常量池

当Java类被加载时,静态常量池中的内容会被存入运行时常量池,也就是被加载到方法区。这个过程中,会进行动态链接,也就是把这些符号引用转换为直接引用。

字符串常量池

这里我们只讨论jdk1.8。上面我们提过,静态常量池中包含两种:字面量和符号引用。字面量中又包含了 :整型字面量、浮点型字面量、字符串字面量等等。

当静态常量池加载到内存变成运行时常量池时,将字符串字面量加载到字符串常量池中,在jdk1.8的版本中,字符串常量池存放在堆中。

在Java中,当我们创建一个字符串字面量(例如,String s = "hello")时,Java会首先在字符串常量池中查找是否已经存在值为"hello"的字符串。如果存在,Java就会返回这个已经存在的字符串的引用;如果不存在,Java就会创建一个新的字符串,放入字符串常量池,并返回这个新字符串的引用。这就是为什么两个相同的字符串字面量的引用往往是相等的。 这样做的主要目的是为了减少string对象的创建,节约内存。

intern方法

intern方法是一个native方法。当调用 s.intern() 方法时,如果字符串常量池中已经包含一个等于 s 的字符串(用 equals(Object) 方法进行比较),那么返回常量池中的字符串。否则,将引用对象放入串池中。

String s1 = new String("hello");
String s2 = "hello";
String s3 = s1.intern();

在上述代码中:

当程序执行new String("hello")的时候,会发生以下两件事情:

1. "hello"这个字符串字面量会被放入字符串常量池。如果常量池中已经存在"hello",则不会创建新的字符串对象。

2. 在堆内存中,创建一个新的String对象,这个对象的值是字符串常量池中"hello"这个字符串的引用。

之后执行s2=”hello“。 此时因为串池中已经存在”hello“,所以s2直接指向了串池中的地址。

之后再执行s3=s1.intern()。由于s1.intern执行的时候,串池中已经存在了”hello“,因此s3也指向了串池中的地址。因此:

s1 == s2  // return false

s2 == s3 // return true

在换一个例子:

String s1 = new String("he") + new String("llo");
//String s3 = "hello"; //这行执行与否将对结果产生影响
String s2 = s1.intern();
System.out.println(s1 == s2);

上述代码中,如果s3语句不被执行,则s1==s2 // true。 如果s3语句执行,则s1==s2 //false。

分析:

1、当执行第一句的时候,在串池中将会创建”he“ 和“llo” 这两个对象,同时会在堆中创建 两个普通的对象。然后生成s1 指向堆中的一个普通对象,对象的内容为“hello”。

2、如果s3的语句执行,则会在串池中创建“hello” 对象。如果不执行,则串池中没有“hello”这对象。

3、当执行s1.intern()的时候,如果s3执行,则说明串池中有“hello”对象,那么s1.intern()将会返回串池中的对象,如果s3未执行,则串池中没有,则将s1对象(全部数据)添加进行串池,并返回引用。

4、因此s1==s2的结果完全取决于s3是否执行。

底层原理

在jvm的c++方法如下:

在jvm_InternString方法中,主要执行StringTable::intern()。

基础包装类型的常量池

整型常量池

Integer i = 126;
Integer j = 126;
i == j //true

上述代码为什么返回true呢? 按理说i和j是两个不同的Integer对象,他们的内存地址是不一样的,应该返回false,但是结果缺返回true。

通过对class解析,看到jvm底层的指令为:
 

 0 bipush 123
 2 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
 5 astore_1
 6 bipush 123
 8 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
11 astore_2
12 return

可以看到Integer i=123调用的是Integer.valueOf(),那么这个方法的内部怎么实现的呢?

public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
}

可以看到,当时i的值介于low和high之间的时候,实际上返回的是一个缓存对象。如果非介于low和high之间,则new一个新的对象。

而low和high对应的值为-128和127。因此返回的i和j是相等的。

同理,其他的包装类型也是如此。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值