jvm之String Table

1.String的基本特性

String:字符串,使用一对" "引起表示,String声明为final的,不可被继承,实现了Serializable接口,表示支持字符串被序列化,在jdk8及以前内部定义了final char[] value用于存储字符串数据。jdk9时改为byte[]。

String代表不可变的字符序列,简称不可变性,当字符串重新赋值时,需要重写指定内存区域赋值,不能使用有value进行赋值,当对现有的字符串进行操作时,也需要重新指定内存赋值,不能使用原有的value进行赋值。当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值,通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串声明在字符串常量池中。

String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后会直接造成影响就是调用String.intern时性能可能会大幅度下降,使用-XX:stringTableSize的长度。

在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快,StringTableSize设置没有要求,jdk7中,StringTable的长度默认值是60013,StringTableSize没有要求,jdk8开始,设置StingTable的长度的话,1009是可能的最小值。

2.String的内存分配

java 6及以前,字符串常量池放在永久代,java 7中Oracle的工程师对字符串的逻辑做了很大的改变,即将字符串常量池的位置调整到java堆,所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优应用时仅需调整堆大小就可以了,字符串常量池概念原本使用比较多,但是这个改动使得我们有足够的理由让我们重新考虑在java 7中使用String.intern(),java 8元空间,字符串常量在堆。

3.字符串拼接操作

①常量与常量拼接结果常量池,原理是编译期优化。

②常量池中不会存在内容的常量。

③只要其中一个是变量,结果就在堆中,变量是拼接原理是StringBuilder。

④如果拼接的结果调用itern()方法,则主动将常量池中还没有的字符串对象,放在池中,并返回此对象地址。

举例:

public class Demo1 {
    public static void main(String[] args) {
        String s1="a"+"b"+"c";
        String  s2="abc";
        System.out.println(s1==s2);// true
        String s3="helloWorld";
        String s4="hello";
        String s5=s4+"World";
        System.out.println(s3==s5);//false
    }
}

由上面的节码指令可知,s1已经在字符串常量池创建了“abc”字符串常量,当s2第二次去找找是否有“abc”,发现有,所以s2指向“abc”在常量池的地址,所以s1==s2为true,s3在常量池中创建“helloWorld” ,s4在常量池中创建“hello”,而s5在堆中new了一个StringBuilder对象,然后调用append方法拼接“hello”(底层是新建一个StringBuilder对象和和一个String对象),因为s3的地址在常量池中而,s5中的地址在堆中,s3==s5为false。

4.intern()的使用

①如果不是用双引号声明的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。比如:String my=new String("I love tongyerui").intern();

②也就是说,如果在任意字符串上调用String.intern方法,那么其返回结果所指向的那个实例,必须和直接以常量形式出现的字符串实例完全相同,因此,下列表达式的值必定是true:("a"+"b"+"c").intern=="abc".

③通俗点讲,Interned String就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被放在字符串内部池(String Intern Pool)。

public class Demo1 {
    public static void main(String[] args) {
        String s1="hello";
        String s2=new String("hello");
        String s3= s2.intern();
        System.out.println(s1==s2);// false
        System.out.println(s1==s3);// true
    }
}

 上面是例子中内存中指向。

 /**
         * String s=new String("1");
         * 创建了两个对象
         *     对空间中一个new对象
         *     字符串常量池中一个字符常量“a”(注意:此时字符串已经有“a”)
         *   s.intern();由于字符串常量池已存在“a”
         *
         * s指向的的是堆空间中的对象地址
         * s1指向的堆空间中常量池中“1”的地址
         * 所以不相等
         *
         */
        String s=new String("1");
        s.intern();
        String s1="1";
        System.out.println(s==s1);// jdk6 false,jdk7/8 false
        /**
         * String s2=new String("1")+new String("1");
         *  等价于new String("11"),但是常量池中并不是不生成字符串“11”;
         * s2.intern();
         *   由于此时常量池中并无“11”,所以把s2中记录的对象地址放入常量池
         *
         *   所以s2和s3指向同一个地址
         *
         */
        String s2=new String("1")+new String("1");
        s2.intern();
        String s3="11";
        System.out.println(s2==s3);// jdk6 false,jdk7/8 true

下面是jdk6vsjdk7/使用intern() 

 总结:jdk1.6中,将这个字符串对象尝试放入串池,如果串池中有,则不会放入。返回字符串中的对象的地址,如果没有,就把此时对象复制一份,放入串池,并返回池中的对象地址。jdk1.7起,将这个字符串对象尝试放入常量池,如果池中有,则并不放入,返回已有的串池的对象的地址,如果没有,则会把对象地址复制一份,放入串池,并返回串池中的引用地址。

5.G1的String去重操作

当垃圾收集器工作的时候,会访问堆上存活的对象。堆每个访问的对象都会检查是否是侯选的去重的String对象,如果是,把这个对象的一个引用插入到队列中等待后续的处理,一个去重的线程在后台运行,处理在这个队列。处理队列的一个元素意味着从队列删除这个元素然后尝试去重它引用的string对象。使用一个hashtable来记录所有的被String对象使用不重复的char数组,当去重的时候,会查这个hashtable,来看堆上是否数组的已经存在一个一模一样的char数组,如果存在,String对象会被调整引用那个数组,释放对原来的数组的引用,最终会被垃圾收集器回收掉。如果查找失败,char数组被插入到hashtable,这样以后的时候就可以共享这个数组了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值