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

什么是常量?
用 final 修饰的成员变量表示常量,值一旦给定就无法改变!

final 修饰的变量有三种,分别表示三种类型的常量。

  • 静态变量
  • 实例变量
  • 局部变量

一、三种常量池描述

三种常量池的思维导图

符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量

只要使用时能无歧义地定位到目标即可(它与直接引用区分一下,直接引用一般是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。

符号引用在执行某个类的解析阶段会替换成直接引用,详情看下图。

二、三种常量池的关系

三种常量池的关系

三、方法区和常量池(class constant pool)

方法区

Class文件中除了有类的版本字段方法接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

方法区的class文件信息

常量池

常量池的每一项常量都是一个表,共有如下表所示的11种各不相同的表结构数据。

这每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。

每种不同类型的常量类型具有不同的结构。

常量池内容

四、举例说明

public class HelloWorld {

   public static void main(String []args) {
        String str1 = "abc";
        String str2 = new String("def");
        String str3 = "abc";
        String str4 = str2.intern();
        String str5 = "def";

        System.out.println(str1 == str3);//true
        System.out.println(str2 == str4);//false
        System.out.println(str4 == str5);//true
   }

}

回到上面的那个程序,现在就很容易解释整个程序的内存分配过程了。

  1. 首先,在堆中会有一个”abc”实例,全局 StringTable 中存放着 ”abc” 的一个引用值;

  2. 然后在运行第二句的时候会生成两个实例,一个是 ”def” 的实例对象,并且StringTable 中存储一个 ”def” 的引用值,还有一个是 new 出来的一个 ”def” 的实例对象,与上面那个是不同的实例;

  3. 当在解析 str3 的时候查找 StringTable,里面有 ”abc” 的全局驻留字符串引用,所以 str3 的引用地址与之前的那个已存在的相同;

  4. str4 是在运行的时候调用 intern() 函数,返回 StringTable 中 ”def” 的引用值,如果没有就将 str2 的引用值添加进去,在这里,StringTable 中已经有了 ”def” 的引用值了,所以返回上面在 new str2 的时候添加到 StringTable 中的 “def” 引用值;

  5. 最后 str5 在解析的时候就也是指向存在于 StringTable 中的 ”def” 的引用值。

那么这样一分析之后,下面三个打印的值就容易理解了。

  1. 上面程序的首先经过编译之后,在该类的 class 常量池中存放一些符号引用

  2. 然后类加载之后,将 class 常量池中存放的符号引用转存到运行时常量池中。

  3. 然后经过验证。

  4. 准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中 str1 所指向的 ”abc” 实例对象),然后将这个对象的引用存到全局 String Pool 中,也就是 StringTable 中。

  5. 最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询 StringTable,保证 StringTable 里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

五、常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,比 equals() 快。对于两个引用变量,只用判断引用是否相等,也就可以判断实际值是否相等。

注意

  • java中基本类型的包装类的大部分都实现了常量池技术;
  • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。

六、 总结

  1. 全局字符串常量池在每个 JVM 中只有一份,存放的是字符串常量的引用值。

  2. class 常量池是在编译的时候每个 class 都有的,在编译阶段,存放的是常量的符号引用。

  3. 运行时常量池是在类加载完成之后,将每个 class 常量池中的符号引用值转存到运行时常量池中。也就是说,每个 class 都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

参考资料

[1] java常量池-字符串常量池、class常量池和运行时常量池
[2] Java提高篇之常量池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值