运行时常量池在哪里_关于字符串池与常量池

字符串池与常量池是完全不同的两个东西,但是很多地方都喜欢把它们混为一谈,很容易让初学者产生误解,在这里我想好好讨论一下它们。

字符串池也可以被称为字符串常量池,我认为这个名称就是产生误解的根源,有些人说着说着就把字符串三个字省略了,只剩下了常量池... 所以为了避免误解,我建议在指代字符串对象的缓存池的时候,就直接称之为字符串池

1 常量池

常量池分为两个类型,一是.class文件中静态的常量池,二是.class文件中的静态常量池被加载到JVM中而形成的运行时常量池

1.1 静态常量池

.class文件中的常量池可以看作一个数组,数组中存储了一些常量,当需要在字节码指令中用到这个常量的时候,就通过数组的索引来访问它。

看下面的代码:

 String m = "hellohellohellohellohello";
 String n = "hellohellohellohellohello";

它在字节码中将会是这种形式:

 // 常量池:
 #1 hellohellohellohellohello
 #2 ...
 ...
 ----------------------------
 ​
 String m = #1;
 String n = #1;

当然,这只是一个简化的版本,实际上要更加复杂 (实际的版本可以看文章末尾参考资料部分里面贴出的那个回答,目前可以先只考虑简化的版本)

注意,在这个里面存储的字符串常量只是一个简单的UTF8编码的字节序列,而不是Java的字符串对象,它就和你在一个txt文本中存储的字符串一样,我们用UTF8格式来打开一个.class文件,可以看到hellohellohellohellohello是可以被解析的:

eb493a5e719f70563808116f20bf8ec7.png

1.2 运行时常量池

理解了静态的常量池之后,运行时常量池就很容易想明白了。简单来说,运行时常量池就是.class文件中的静态常量池在JVM中的运行时表示,每一个.class文件的静态常量池都会生成一个对应的运行时常量池。等到JVM在解释String m = #1这条指令时,它可以去这个类的运行时常量池中查找#1的定义。

2 字符串池

字符串池是Java为了重用String对象而设置的一个缓存池,Java1.7之前设置在方法区上,保存的是String对象;Java1.7之后设置在堆上,保存的是String对象的引用,String对象本身存在于堆上的其他位置。下文中以Java1.7之后的情况为标准。

继续上面的例子。当JVM在解释String m = #1时,它已经从运行时常量池拿到了相应的UTF8序列,接下来,它会在字符串池中寻找和这个UTF8序列对应的String对象,并把这个对象的引用赋值给m。你可能会好奇这个String被创建的时机,根据R大的这篇文章,在这条语句所在的类被加载时,如果字符串池中已经存在对应的对象了,那么就什么都不做,如果不存在,就会创建一个对应的String对象,并把其引用放入池中。

除了字符串池,IntegerLong等Wrapper类型也有自己的缓存池,比如Integer会缓存从-128~127的Integer对象,当使用字面量赋值或者Integer.valueOf()时,如果池中存在相应的对象,就会返回池中的对象,只有当池中没有时才会在堆上创建新对象。

不过,和字符串池不同的时,这些Wrapper池不会像字符串池一样可以增长,也就是池中的对象数目是固定的,Integer池中只会有-128~127。

3 涉及String的其他内容

在这里我收集了一些关于String的一些有趣和需要注意的信息,欢迎大家补充

强调,下面的代码只在JDK 11的环境下进行了测试,不同的JDK中可能会有不同的行为,同样欢迎大家补充

3.1 在Java 7之后String.intern()的行为

考虑下面的代码:

 String a = new String("hello");
 a.intern();

a.intern()会在字符串池中查找是否有一个字符串引用所指向的对象的值是"hello",如果有,就直接返回池中的引用;如果没有,就把a指向的对象的引用放入池中,在返回该引用。所以:

 String a = new String("hello");
 return a == a.intern(); 
 ​
 // 返回false.
 // a调用intern方法时,字符串池中已经存在表示hello这个字符序列的String对象引用了(因为"hello"字面量的存在),所以a.intern返回的引用和a肯定不同

------------------------------------------------------------------------------

 String a = new String(new char[] {'h', 'e', 'l', 'l', 'o'});
 return a == a.intern();
 ​
 // 返回true
 // new String(new char[] {'h', 'e', 'l', 'l', 'o'})因为没有出现"hello"字面量,所以池中没有表示hello的对象引用,那么a.intern会将a放入池中再返回,返回结果自然和a相等

3.2 两个字符串相加时创建的新对象(的引用)会被放入字符串池中吗:

结论:除非是两个字面量直接相加,否则都不会被放入字符串池中

 String m = "ja" + "va";
 String n = new String(new char[]{'j', 'a', 'v', 'a'});
 ​
 System.out.println(n.intern() == n);
 ​
 // 输出:false
 ​
 // 分析:
 // 根据n.intern() != n 可以得出相加后的"java"对象被放入了字符串池。实际上,编译器会将"ja" + "va"直接连起来变成"java",所以字节码中是m = "java",这样一来在解释器遇到这一行时,一个"java"对象就会在堆上被创建,并且它的引用会被放入字符串池中

---------------------------------------------------------------------------

 String m1 = "ja";
 String m2 = "va";
 String m = m1 + m2;
 String n = new String(new char[]{'j', 'a', 'v', 'a'});
 ​
 System.out.println(n.intern() == n);
 ​
 // 输出:true
 ​
 // 分析:
 // 根据n.intern() == n 可以得出相加后的"java"对象没有被放入字符串池。
 ​
 // 可以自己测试当m1 = new String("ja"), m2 = new String("va")的情况,得到的结果和本例是一样的

4 总结

运行时常量池中的字符串是UTF8序列,字符串池中存储的是Java的String对象(的引用),不要把两者弄混了。

5 参考资料

更详细的内容比如字面量对应的String对象被创建的时机、对字节码的分析可以看 @木女孩大大的这篇回答:

Java 中new String("字面量") 中 "字面量" 是何时进入字符串常量池的?​www.zhihu.com

其他参考:

String pool vs Constant pool​stackoverflow.com
5735cde254a831de1bf93a51ff4f3afe.png
Why does the behavior of the Integer constant pool change at 127?​stackoverflow.com
5735cde254a831de1bf93a51ff4f3afe.png
请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧​www.iteye.com

有帮助的话麻烦点个赞奥~ 另外也欢迎关注我的专栏!

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值