JVM 常量池

目录

 

1.常量池

1.1 运行时常量池

1.2 String常量池(串池)

2. 面试案例

2.1 字符串是否相等问题

JDK 1.6 中的 String.intern()

JDK 1.7 中的 String.intern()

2.2  String创建几个对象


 

1.常量池

常量池是一种存储常量的表格,它包含了编译期生成的各种字面量和符号引用。

CLass文件常量池存下内容:

字面量:

  • 文本字符串(代码中用双引号包裹的字符串部分的值)
  • 被声明为finnal的常量
  • 基本数据类型的值
  • 其他  

符号引用:

  • 类符号引用:类的完全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

1.1 运行时常量池

当类被加载的时候常量池这部分信息将会被存放到运行时常量池中,并把里面的符号地址转化为内存真实地址。运行时常量池就是将字节码文件中的信息放入方法区中,属于方法区的一部分。运行时常量池是类在加载阶段完后,将class常量池中的符号引用转存到运行时常量中;每个类都有一个运行时常量池,可以用来动态获取类信息。

 

1.2 String常量池(串池)

String常量池的底层实现: 在HotSpot JVM中,String常量池的底层使用了哈希表(HashMap或类似的数据结构)来存储字符串字面量和对应的字符串对象引用。这个哈希表的键是字符串字面量,而值是字符串对象的引用。这样可以快速查找字符串字面量并重用已经存在的字符串对象。

String直接赋值和使用new实例对象: 当使用直接赋值方式创建字符串时,例如 String s1 = "Hello";,如果常量池中已经存在 "Hello" 这个字符串字面量,那么s1将指向常量池中的这个字符串。如果常量池中没有,它会在常量池中创建一个 "Hello" 字符串字面量,然后s1将指向这个新创建的字符串。

当使用 new 操作符创建字符串对象时,例如 String s2 = new String("Hello");,无论常量池中是否已经存在 "Hello" 字符串字面量,都会在堆内存中创建一个新的字符串对象,s2指向堆内存中的这个对象。如果常量池中已存在 "Hello" 字符串字面量,它不会再次在常量池中创建,但仍然会在堆内存中创建一个新的对象。然后堆里的对象的value指向常量池内的这个Hello字符串。

jdk 1.8中String常量池存在堆中

2. 面试案例

2.1 字符串是否相等问题

 

        String s1="a";
        String s2=new String("a");
        System.out.println(s1==s2);//false

 由上面的讲解可以得知为false。

以下看综合案例

        String s1="a";
        String s2=new String("b");
        String s3="a"+"b";
        String s4=s1+s2;
        String s5="ab";
        String s6=s4.intern();


        System.out.println(s3==s4);//false
        System.out.println(s3==s5);//true
        System.out.println(s3==s6);//true

        String x2=new String("c")+new String("d");
        String x1="cd";
        x2.intern();
        System.out.println(x1==x2);//false

String s3="a"+"b":

编译器会自动优化,组合这两个字面量变成“ab”,之后这个字面量“ab”和在堆中创建的String对象的引用会被放到String常量池内。所以s3==s5为true。

String s4=s1+s2:

编译器内部对于String字符串变量拼接,会创建一个StringBuilder,对于每一个要拼接的内容,调用append进行添加,最后在使用toString()方法返回成字符串。StringBuilder对象存放在堆中,StringBuilder的toString方法得到的对象也是在堆中,并没有被添加到常量池;s4是在堆中,而s3是在字符串常量池中,所以s3==s4为false

String.intern()

JDK 1.6 中的 String.intern()

在JDK 1.6中,String.intern() 方法的行为有以下特点:

  1. 当调用 String.intern() 方法时,如果字符串常量池中已经包含了一个等于此 String 对象的字符串(内容相同),则返回字符串常量池中的这个字符串的引用。

  2. 如果字符串常量池中没有包含等于此 String 对象的字符串,那么会将该 String 对象添加到字符串常量池中,并返回字符串常量池中的这个字符串的引用。

  3. 在JDK 1.6中,字符串常量池是固定大小的,不会自动调整大小。这可能导致字符串常量池中的字符串数量有限,并且在大量字符串被调用 intern() 的情况下,可能会导致性能问题或 OutOfMemoryError 异常。

JDK 1.7 中的 String.intern()

在JDK 1.7中,String.intern() 方法的行为发生了变化:

  1. 与JDK 1.6不同,JDK 1.7中的字符串常量池不再是一个固定大小的区域。它被移到了堆内存,这意味着字符串常量池的大小可以动态地调整以适应应用程序的需求。

  2. 当调用 String.intern() 方法时,如果字符串常量池中已经包含了一个等于此 String 对象的字符串(内容相同),则返回字符串常量池中的这个字符串的引用,与JDK 1.6相同。

  3. 如果字符串常量池中没有包含等于此 String 对象的字符串,那么intern() 方法会将当前字符串对象的引用添加到字符串常量池中,并返回该引用,而不是创建一个新的字符串对象。这是JDK 1.7中的区别之一。

s3==s6:

当s6=s4.intern()的时候常量池中有ab字符串,故不放入常量池,s4依旧是堆对象,而s6指向的是常量池中的对象,故为true。

2.2  String创建几个对象

String str="a"+new String("b");

创建五个对象:

对象1: new StringBuilder()
对象2: 常量池中的"a"
对象3: new String(“b”)
对象4: 常量池中的"b"
对象5 :StringBuilder执行toString方法生成的new String("ab"),ab不会放在字符串常量池中。

 

String str=new String("a")+new String("b");

 创建六个对象:


对象1: new StringBuilder()
对象2: new String(“a”)
对象3: 常量池中的"a"
对象4: new String(“b”)
对象5: 常量池中的"b"
对象6 :new String(“ab”)

 

注:

在JDK1.7以前,字符串常量池 和 运行时常量池都存放在方法区中,对方法区的实现成为永久代

 在JDK1.7,字符串常量池 从方法区转移到了堆中,运行时 常量池还是在方法区中

 在jdk1.8以后,取消了永久代,取而代之的是元空间。运行时常量池和静态常量池都存放在元空间中,而字符串常量池依然在堆空间中(只是和堆共享空间,但和堆互相隔离)

 

StringBuilder最后也用到了new String,但为什么没有入池 ?
通过new String(“ab”) 创建对象时,是通过双引号修饰的字面量来创建的,而 StringBuilder() 拼接的字符串,不会进入字符串常量池。可以理解为,在代码中写的字符串字面量才会进入字符串常量池
 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山河亦问安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值