String
- 声明为final
- 实现了Serializable、Comparable(可序列化、比较大小)
- 内部结构
- JDK8:char[]
- JDK9:byte[],节省资源,StringBuffer、StringBuilder也发生改变
- 代表不可变序列,具有不可变性
- 对字符串重新赋值,需要重新指定内存空间赋值,不使用原来的value进行赋值
- 通过字面量给字符串进行赋值,字符串声明在字符串常量池中
- String.intern方法可将字符串存放至字符串常量池
字符串常量池
- 不会存储相同内容的字符串
- 字符串常量池是Hashtable
- JDK6,固定大小1009
- JDK7,默认长度60013
- JDK8,1009是StringTable可设置的最小值
- JDK7后,字符串常量池的位置由永久代变为堆中(永久代容量小、永久代回收频率低,对String的使用等不友好)
字符串拼接
- 常量和常量(字面量、final修饰的变量)拼接结果放在常量池,编译期优化
- 只要其中一个是变量,会使用StringBuilder的append方法,最后toString使用String构造器
- 拼接的结果调用了intern方法,主动将常量池没有的字符串对象放入池中,并返回对象地址
intern
- intern方法会从字符串常量池中查找当前字符串是否存在,若不存在,就将字符串放入常量池
- intern方法返回结果所指向的类实例与以常量形式出现的字符串实例完全相同
intern版本比较
- jdk6:如果字符串常量池没有字符串,就将对象复制一份放入字符串常量池
- jdk7起:如果字符串常量池没有字符串,就将对象的引用地址复制一份放入字符串常量池
public class InternTest {
@Test
public void test1() {
String s1 = new String("011");
s1.intern();
String s2 = "011";
System.out.println(s1 == s2);
String s3 = new String("0") + new String("1");
s3.intern();
String s4 = "01";
System.out.println(s3 == s4);
}
@Test
public void test2() {
String s1 = new String("01") + new String("1");
String s2 = s1.intern();
System.out.println(s1 == "011");
System.out.println(s2 == "011");
}
}
String去重
- 堆存活数据集合里面String对象占25%
- 堆存活集合重复的String对象由13.5%
- String平均长度为45
- G1垃圾收集器实现自动持续对重复的String对象去重,避免浪费内存
G1去重实现
- 访问堆中的对象,检查是否是候选的去重对象
- 如果是,把对象的引用插入到队列等待后续结果,一个去重的线程在后台运行处理队列,处理它意味着重队列删除元素,然后尝试去重它引用的String对象
- 使用StringTable记录所有被String对象使用的不重复char数组,当去重的时候,会查这个hashtable,看堆上是否有完全一样的char数组(jdk8以前String底层实现是char)
- 如果存在,String对象会调整引用那个数组,释放堆原来数组的引用,最终会被垃圾收集器回收
- 查找失败,就将char数组存放到Stringtable,以后可以共享此数组
参数
- 开启去重:UseStringDuplicationStatistics(bool)
- 打印去重信息:PrintStringDuplicationStatistics(bool)
- 设置候选年龄:StringDuplicationAgeThreshold(uintx)