jvm字符串常量池问题一文解决

概要

  • jvm字符串常量池问题无非就是关于一次new String创建了几个对象等问题,这些问题网上虽然资料很多,但是你会发现很多人说的都不一样,甚至有矛盾。。。曾经也是困扰了我许久,所以今天写下这篇文章,省去那些长篇大论,尽可能用最精简的方式把这个事情讲明白。
  • 以下所有关于字符串常量池简称为常量池,实际上常量池还包括运行时常量池剩下的部分,但是此处不在我们的讨论范围内。

例子

例1

 	String s = new String("a");

问题一、该代码创建了几个对象?

答案是2个:

  1. 一个是在堆内存中创建的普通String类型的实例对象,和我们平时创建的对象是同一种东西
  2. 一个则是会在常量池中创建一个 "a"常量。(姑且可以把这里的“对象”理解成实际数据,因为实际上常量池中的"a"并不是字面意思上的“对象”,而是一个常量,下文同理)。

问题二、s变量指向哪个对象?

答案:s变量指向堆内存的对象,而不是常量池中的对象。

例2

String s1 = new Stirng("a") + String ("a");
String s2 = new Stirng("b") + String ("c");

问题一、第一行代码创建了几个对象?

答案是4个:

  1. 在堆内存中创建两个值都是"a"的不同的对象,这两个对象是内存地址是不同的。
  2. 在堆内存中创建一个值是"aa"的对象,并且s1也指向该对象。
  3. 在常量池中创建一个值为"a"的常量。

问题二、第二行代码创建了几个对象?

答案是5个:

  1. 在堆内存中创建一个值是"b"的对象。
  2. 在堆内存中创建一个值是"c"的对象。
  3. 在堆内存中创建一个值是"bc"的对象,并且s2也指向该对象。
  4. 在常量池中创建一个值为"b"的常量。
  5. 在常量池中创建一个值为"c"的常量。

例3

String s1 = "abc";
String s2 = "ab"
String s3 = s2 + "c";
boolean ifEqual = s1 == s3;

问题一、第1行和第2行的代码分别创建了几个对象?

答案是分别为1个:

  1. 第一行代码在常量池中创建了一个值为"abc"的常量
  2. 第二行代码在常量池中创建了一个值为"ab"的常量

问题二、ifEqual变量的值为多少?

答案是false:

  1. jdk规定只有直接使用双引号创建的字符串才会绕过堆内存去常量池创建或寻找对象,所以s3实际上是创建了两个对象:一个是常量池中的c常量;另一个则是堆中值为abc的对象。而s3变量会指向堆中值为abc的对象,而不是常量池中的abc常量。所以s1与s3变量存储的内存地址肯定不同。

例4

String s1 = "abc";
String s2 = "ab"
String s3 = "ab" + "c";
boolean ifEqual = s1 == s3;

问题一、ifEqual变量的值为多少?

答案是true:

  1. java编译优化,如果加号两边都是双引号而不是变量,就会把String s3 = “ab” + "c"这个代码优化成String s3 = "abc"去运算。
  2. 实际上String s3 = “ab” + "c"这个语句是创建了0个对象,为什么呢?因为String s1 = “abc”;这个语句已经在常量池中创建了一个abc常量了,所以只需要将s3指向这个常量即可,这也解释了为什么s1 == s3。

思考

  • 通过以上例子相信你可以大体搞清楚字符串的创建过程了,下面我们来讲一些比较深层次的问题。

一、关于intern()函数

  • 不用深究细节,该函数的作用就是:“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”

二、关于字符串常量池的存放位置

  • jdk1.6下,字符串常量池存放在永久代中,也就是非堆(当然你也可以理解为方法区)。
  • jdk1.7下,字符串常量池存放在堆中。
  • 注意,这里说的都是字符串常量池,运行时常量池剩下的东西都还是存放在永久代中的,并且在jdk1.8下移到了元空间中

三、jdk1.6和jdk1.7下intern()函数的区别

  • 由于jdk1.7中常量池存放在堆中,所以intern()函数做了优化,优化内容是:“如果某个字符串对象调用了intern()方法来将字符串放入常量池,那么常量池中只会存放指向该字符串在堆中对象的引用,而不是直接在常量池中创建一个常量”。 很明显,这样可以减少数据冗余。
  • 这么说你可能还是不太理解。我们来看个例子。
String s1 = new String("a") + new String("a");
s1.intern();
String s2 = "aa";
boolean ifEqual = s1 == s2;
  1. 在jdk1.6下
  • 这里的ifEqual运行结果是false,为什么呢?我们来走一遍。
  • 第一行代码创建了4个对象,分别是两个存放于堆中的值为a的对象、一个存放于堆中的值为aa的对象、一个存放于常量池的a常量。(注意:此时常量池中是没有aa常量的
  • s1变量指向堆中的值为aa的对象。
  • s1调用intern()函数,先去检查常量池中有无aa常量,显然此时是没有的。
  • 由于此时intern()函数是未优化版本,所以会直接在常量池中创建一个aa常量,并让s2指向常量池中的aa地址,所以很明显s1和s2指向的内存地址不是同一个。
  1. 在jdk1.7下
  • 这里的ifEqual运行结果是true。

  • 原因很简单,因为jdk1.7的intern()函数作了优化,所以s1调用intern()函数时,并不会直接在常量池中创建aa常量,而是创建一个指向堆中值为aa的对象的引用(也就是一个地址或指针),所以s2虽然指向的是常量池中的数据,但是由于常量池中的数据实际是指向堆中值为aa的对象的引用,所以相当于s2间接指向了该对象,所以s1和s2肯定是相等的,因为他们指向的是同一个内存地址。

  • 现在是不是懂了?下面我要再讲最后一个例子。

String s1 = new String("a") + new String("a");
//s1.intern();
String s2 = "aa";
boolean ifEqual = s1 == s2;
  • 注意,这里无论是jdk1.7还是jdk1.6,ifEqula都是false。
  • 在常量池中创建对象有三个途径:1.通过new方法创建。2.通过双引号创建。3.通过intern()方法创建。
  • jdk1.7只对intern()函数作了优化,如果注释了s1.intern();这句代码,那么在常量池中创建aa常量的工作就交给了String s2 = “aa”;这句话,在这种情况下,无论是jdk1.7还是jdk1.6是都会在常量池中创建aa常量的,并不会创建引用。

小结

  1. 以上的例子肯定是不全的,最好自己去IDE中造几个例子验证一下,加深一下理解。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值