字符串常量池-StringTable

常量池与StringTable的关系

源代码

public class Main extends ClassLoader{
    public static void main(String[] args){
        String a = "a";
        String b = "b";
        String c = "ab";
    }
}

反编译
在这里插入图片描述
在这里插入图片描述

  1. 字符串字面量在类加载时存入到常量池,常量池中的信息都会被加载到运行时常量池中,此时字符串还是常量池中的符号,并不是字符串对象
  2. ldc指令会将常量池中对应编号的符号变为字符串对象,如ldc #2 会将常量池中的“a”变成字符串对象
  3. StringTable是哈希结构,不可扩容
  4. 在ldc字符串对象时,会先去StringTable找这个字符串对象,如果没有就会从常量池中找到这个符号,并变成字符串对象

字符串拼接

public class Main extends ClassLoader{
    public static void main(String[] args){
        String a = "a";
        String b = "b";
        String ab = a+b;//new StringBuilder().append(a).append(b).toString();
        String cd = "c"+"d";
    }
}

在这里插入图片描述

  1. 如果拼接的字符串中出现了字符串对象(如 new String(“a”))、字符串对象的引用(如String a = “a”;中的引用a);将会创建一个new StringBuiler()通过append方法来一步步构建最终结果
  2. 如果拼接的字符串都是字面量,则会在通过编译期优化,直接得到最终结果

intern方法

jdk1.8

intern方法会将字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,最后将串池中的对象返回

public class Main extends ClassLoader{
    public static void main(String[] args){
        String a = new StringBuilder().append('a').toString();//通过StringBuilder构建的字符串对象不会放入串池中
        String b = a.intern();
        String c = "a";
        System.out.println(b==a); // true,b是通过intern方法后从串池中得到的,之前串池中没有“a”,所以a变量指向的对象会放入串池中,然后的发哦a
        System.out.println(c==a); //true,此时串池中的对象就是a
    }
}

jdk1.6

intern方法会将字符串对象放入串池,如果有则不会放入,如果没有则将此对象复制一份,然后将副本放入串池,会将串池中的对象返回

public class Main extends ClassLoader{
    public static void main(String[] args){
        String a = new StringBuilder().append('a').toString();//通过StringBuilder构建的字符串对象不会放入串池中
        String b = a.intern();
        String c = "a";
        System.out.println(b==a); // false,a的副本放入了串池,所以a并不是串池中的对象,a仍然在堆中
        System.out.println(c==a); //false,a的副本放入了串池,所以a并不是串池中的对象,a仍然在堆中
        System.out.println(b==c); // true,b是通过intern方法得到的串池中的对象,c是通过字面量指向的串池中的对象
    }
}

总结

  1. 常量池中的字符串仅仅是符号,在第一次用到时,才会变为对象
  2. StringTable利用串池的机制,来避免重复创建字符串对象
  3. 字符串变量的拼接原理是StringBuilder构建
  4. 字符串常量的拼接原理是编译期优化
  5. 可以使用intern方法,主动将串池中还没有的字符串对象放入串池

StringTable面试题

public class Main extends ClassLoader{
    public static void main(String[] args){
       String s1 = "a";
       String s2 = "b";
       String s3 = "a"+"b";
       String s4 = s1+s2;
       String s5 = "ab";
       String s6 = s4.intern();


        System.out.println(s3==s4); // false,s3通过编译器优化,运行时当作“ab”字面量,而s4会经过StringBuilder构建一个新的字符串对象,存在堆中
        System.out.println(s3==s5); // true,s3和s5都指向串池中的“ab”
        System.out.println(s3==s6); // true,s4调用intern方法会返回串池中的“ab”
    }
}

StringTable的位置

jdk1.8

在这里插入图片描述

jdk1.6

在这里插入图片描述

更改原因

  1. 永久代在Full GC时才会触发垃圾回收,Full GC在老年代垃圾回收时才会触发,所以永久代的垃圾回收概率较低;同时,Java程序在运行过程中会有大量的字符串常量进入串池,容易导致永久代内存不足
  2. 在1.8,堆中的StringTable只需要Minor GC就可以触发StringTable的垃圾回收

StringTable垃圾回收

虚拟机参数:

  1. -Xmx10m:将堆空间设置为10m
  2. -XX:+PrintStringTableStatistics:打印StringTable
  3. -XX:+PrintGCDetails -verbose:gc:打印垃圾回收细节
    初始状态
    在这里插入图片描述
public class Main extends ClassLoader{
    public static void main(String[] args){
        int i=0;
        try {
            for (int j = 0; j < 10000; j++) {
                // 往串池中添加字符串
                String.valueOf(j).intern();
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }
}

在这里插入图片描述

StringTable性能调优

StringTable大小调整

  1. StringTable的底层结构类似于哈希表,哈希表的性能和哈希表的桶的个数有关,哈希表桶的个数较多时,哈希表的哈希碰撞较少,查找速率也较快
  2. 通过虚拟机参数:-XX:StringTableSize=20000,可以将桶个数改为两万个
    在这里插入图片描述

为什么用StringTable

在大量字符串对象存在且重复时,重复的数据会占用大量的内存空间,使用StringTable是利用享元模式的思想,共享重复的数据,有利于节省空间

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值