JVM HashTable字符串常量池

String的基本特性

  • 不可变性
  • 实现Comparable接口,可以比较大小
  • 支持序列化
  • JDK1.8底层是char[]数组,JDK1.9底层是byte[]数组加上编码标记
    • 为什么要做这个修改?
      • 为了节省空间,char占两个字节,byte占一个字节。对于需要占用两个字节的字符(比如说)还是使用两个字节存储
  • 字符串常量池中不会存储相同内容的字符串内容
    • 字符串常量池底层就是HashTable,在JDK1.6的时候默认大小是1009,且可以通过虚拟机参数“-XX:StringTableSize”随意设置大小,在JDK1.7的时候默认大小是60013,可以随意设置大小。JDK8的时候默认大小说60013,可设置的最小值为1009。
    • 为什么从1.6->1.7->1.8要对字符串常量池的大小进行修改并限制可设置的最小值呢?
      • 如果字符串常量池大小不够就会发生hash冲突,导致链表过长,链表过长直接造成的影响就是当调String.intern时的性能大幅度下降

字符串常量池内存布局

  • JDK1.6:永久代
  • JDK1.7:堆空间中
  • JDK1.8:堆空间中,元空间替换了永久代

字符串拼接

  • 常量和常量拼接,拼接后的值保存在字符串常量池中
  • 字符串常量池中不会保存相同内容的字符串
  • 只要拼接中有对象,那么拼接后的对象保存在堆中。原理是StringBuilder
  • 如果拼接调用的结果调用intern()方法,会把拼接后的字符串主动保存到字符串常量池中,并且返回此对象地址。如果该字符串已在字符串常量池中则直接返回地址。

从面试题熟悉拼接

567        String s = new String("1");
568        s.intern();
569        String s2 = "1";
570        System.out.println(s == s2);    //F

572        String s3 = new String("a") + new String("b");
573        s3.intern();
574        String s4 = "ab";
575        System.out.println(s3 == s4);   // jdk1.6中是F,1.6以上是T

 

 0 new #4 <java/lang/String>
 3 dup
 4 ldc #5 <1>
 6 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
 9 astore_1
10 aload_1
11 invokevirtual #7 <java/lang/String.intern : ()Ljava/lang/String;>
14 pop
15 ldc #5 <1>
17 astore_2
18 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
21 aload_1
22 aload_2
23 if_acmpne 30 (+7)
26 iconst_1
27 goto 31 (+4)
30 iconst_0
31 invokevirtual #9 <java/io/PrintStream.println : (Z)V>
34 new #10 <java/lang/StringBuilder>
37 dup
38 invokespecial #11 <java/lang/StringBuilder.<init> : ()V>
41 new #4 <java/lang/String>
44 dup
45 ldc #12 <a>
47 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
50 invokevirtual #13 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
53 new #4 <java/lang/String>
56 dup
57 ldc #14 <b>
59 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
62 invokevirtual #13 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
65 invokevirtual #15 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
68 astore_3
69 aload_3
70 invokevirtual #7 <java/lang/String.intern : ()Ljava/lang/String;>
73 pop
74 ldc #16 <ab>
76 astore 4
78 getstatic #8 <java/lang/System.out : Ljava/io/PrintStream;>
81 aload_3
82 aload 4
84 if_acmpne 91 (+7)
87 iconst_1
88 goto 92 (+4)
91 iconst_0
92 invokevirtual #9 <java/io/PrintStream.println : (Z)V>
95 return

 练习一

String s3 = new String("a") + new String("b");
String s4 = s3.intern();    //打印T、T、T
//String s4 = "ab";    打印F、F、T
System.out.println(s3 == s4);
System.out.println(s3 == "ab");
System.out.println(s4 == "ab");

练习二

// 想说明的是new String("ab") 和new String("a") + new String("b")的区别
// new String("ab")会在字符串常量池中创建"ab"
// 而new String("a") + new String("b")不会
// String s1 = new String("ab"); 打印F
String s1 = new String("a") + new String("b");    //打印T
s1.intern();
String s2 = "ab";
System.out.println(s1 == s2);

String中intern()的总结

  • 在JDK1.6中,将这个字符串放入字符串常量池
    • 如果字符串常量池中存在,那么不放入,返回已有字符串常量池中的对象的地址
    • 如果不存在,复制一份此对象,放入永久代中的字符串常量池,并返回字符串常量池中的对象的地址
  • JDK1.7起,将这个字符串放入字符串常量池
    • 如果字符串常量池中存在,那么不放入,返回已有字符串常量池中的对象的地址
    • 如果不存在,把对象的引用地址复制一份,放入到字符串常量池中,并返回字符串常量池中的对象的地址

Intern()对于空间效率的影响以及垃圾回收的测试

不使用Intern()

 使用intern() 

 

         留坑:G1的String去重操作,这里的去重针对的是new String("Xx")在堆中生成的对象,字符串常量池中本来就不包含重复的字符串

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值