超过1W字深度剖析JVM常量池

面试题:String a = "ab"; String b = "a" + "b"; a == b 是否相等

面试考察点

考察目的:考察对JVM基础知识的理解,涉及到常量池、JVM运行时数据区等。

考察范围:工作2到5年。

背景知识

要回答这个问题,需要搞明白两个最基本的问题

  1. String a=“ab”,在JVM中发生了什么?

  2. String b=“a”+“b”,底层是如何实现?

JVM的运行时数据

首先,我们一起来复习一下JVM的运行时数据区。

为了让大家有一个全局的视角,我从类加载,到JVM运行时数据区的整体结构画出来,如下图所示。

对于每一个区域的作用,在我之前的面试系列文章中有详细说明,这里就不做复述了。

57ef7b332dc8a264723cc308d622a43e.png

7df102a718e16d3df790766a5f5fd3cc.png

9726fa6f96cddc3b7160057294a1e894.png

398bbe8061ddf85d9c122654d54a947f.png

971988900aa5418602a50296c717369c.png

571979214d5459cba3dced188e8dfa93.png

a82a4c38f66b1d2ef9a7d7cff6ab0aba.png

46d3f8f720944a37ef70f7581312e4be.png

431393361489c174b2982bac6b6f9cf1.png

5e875ab226741161065ce00e97401d0b.png

68083237f284a83c2dadeed9fca6155d.png

e23757e16b43f402dca126fad13043eb.png

当使用String a=“Hello”这种方式创建字符串对象时,JVM首先会先检查该字符串对象是否存在与字符串常量池中,如果存在,则直接返回常量池中该字符串的引用。否则,会在常量池中创建一个新的字符串,并返回常量池中该字符串的引用。(这种方式可以减少同一个字符串被重复创建,节约内存,这也是享元模式的体现)。

如下图所示,如果再通过String c=“Hello”创建一个字符串,发现常量池已经存在了Hello这个字符串,则直接把该字符串的引用返回即可。(String里面的享元模式设计)

55f3e26bc36fd378d83d71333387e40f.png

dd85dee95122409d10cbae35ad41522d.png

979c1bebaf4d711cdde73abe900d3a1e.png

e32ec4f220065775d87f78b03ef25f89.png

8da41d6105db342d23fb93f4cf79a534.png

77ac70548c77e6472b51641663ce6e8b.png

f0958a8dcd4990e223762b51f070e5ff.png

9b8af071229c2e742838cea3ace1ff54.png

da92afebe46d09a0543cd21b863827d4.png

c3d660e72a741d8071618e9a8d64e08f.png

d31843e252afbadac9f8242ed66496a5.png

ea62975912642182e36bdd39755ce21c.png

b9ac1c81db1bd7a0e193ca9b557d22a0.png

25c3a967209b961ec5956ede4ea3f943.png

66b284420fcf4fc35dfcc3f996a25aec.png

总结:intern方法会从字符串常量池中查询当前字符串是否存在:

  • 若不存在就会将当前字符串放入常量池中,并返回当地字符串地址引用。

  • 如果存在就返回字符串常量池那个字符串地址。

注意,所有字符串字面量在初始化时,会默认调用intern()方法。

这段程序,之所以a==b,是因为声明a时,会通过intern()方法去字符串常量池中查找是否存在字符串Hello,由于不存在,则会创建一个。同理,变量b也同样如此,所以b在声明时,发现字符常量池中已经存在Hello的字符串常量,所以直接返回该字符串常量的引用。

public static void main(String[] args) {
  String a="Hello";
  String b="Hello";
}

OK,学习到这里,是不是感觉自己懂了?我出一道题目来考考大家,下面这段程序的运行结果是什么?

public static void main(String[] args) {
  String a =new String(new char[]{'a','b','c'});
  String b = a.intern();
  System.out.println(a == b);

  String x =new String("def");
  String y = x.intern();
  System.out.println(x == y);
}

正确答案是:

true
false

第二个输出为false还可以理解,因为new String(“def”)会做两件事:

  1. 在字符串常量池中创建一个字符串def

  2. new关键字创建一个实例对象string,并指向字符串常量池def的引用。

x.intern(),是从字符串常量池获取def的引用,他们的指向地址不同,我后面的内容还会详细解释。

第一个输出结果为true是为啥捏?

JDK文档中关于intern()方法的说明:当调用intern方法时,如果常量池(内置在 JVM 中的)中已经包含相同的字符串,则返回池中的字符串。否则,将此String对象添加到池中,并返回对该String对象的引用。

ec95dde340b0ddea104f1c91c18c45d7.png

0a4e1b564bd518cf3ba3529b9ea01dce.png

eba88fa961bea18d16cafb5357d372b8.png

89759f360a7b4d5eac980f15c90263a5.png

4f19ba7aadef765b0b190d8e10527eab.png

public static void main(String[] args) {
  String s1 = "a";
  String s2 = "b";
  String s3 = "ab";
  String s4 = s1 + s2;
  System.out.println(s3 == s4);
}

答案是false

因为上述程序等价于, s3s4指向不同的地址引用,自然不相等。

public static void main(String[] args) {
  String s1 = "a";
  String s2 = "b";
  String s3 = "ab";
  StringBuilder sb=new StringBuilder().append(s1).append(s2);
  String s4 = sb.toString();
  System.out.println(s3 == s4);
}

总结:只有足够清晰的理解了字符串常量池相关的所有知识点,不管面试过程中如何变化,你都能准确回答,这就是知识的力量!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值