• 字符串常量池-StringTable


    常量池与StringTable的关系

    源代码

    public class Main extends ClassLoader{
        public static void main(String[] args){
            String a = "a";
            String b = "b";
            String c = "ab";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    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
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    总结

    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”
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    StringTable性能调优

    StringTable大小调整

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

    为什么用StringTable

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

  • 相关阅读:
    Arduino与Proteus仿真实例-密码门禁控制仿真
    轻松整理电脑文件:按大小归类保存,高效管理你的数据
    WOFOST模型与PCSE模型实践技术应用
    blender 烘焙贴图
    Spring学习|Spring配置:别名、import、依赖注入:构造器注入、Set方式注入(重点)、拓展方式注入
    获取外网IP接口
    maven 重复依赖不同版本 选择规则
    基于jsp+mysql+ssm在线音乐网站-计算机毕业设计
    大数据职业规划
    【leetcode】数组排序
  • 原文地址:https://blog.csdn.net/m0_48468380/article/details/126828473