一、String的基本特性:
具体可以查看网址查看变更具体信息:
http://openjdk.java.net/jeps/254
a、String 的不可变性:
eg(1):
(2)
原有字符串常量池里的"abc" 不变,只是在常量池里新创建了一个"abcdef"
(3)
b、字符串常量池里不能存储相同内容的字符串、
底层实现:
二、String的内存分配:
内存位置发生改变:
StringTable为什么要调整?
(1)permSize默认比较小,
(2)永久代垃圾回收频率比较低
三、String的基本操作:
因为上面字符串常量池已经有"1",所以还是2304
四、字符串拼接操作:
eg(1):
常量与常量的拼接结果一定在常量池中。
上述代码反编译后:
ldc 是加载常量池中的"abc"
eg(2):
只要拼接的时候有一个变量,结果就在堆中,拼接原理是StringBuilder.
变量拼接原理的底层操作:
字节码如下:
通过如上字节码 s1+s2d 执行细节如下:(变量s是我临时定义的)
StrinBuilder s = newStringBuilder();
s.append("a");
s.append("b");
s.toString();----》约等于 new String("ab"); 只不过在常量池里没有"ab";
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前所使用的是StringBuffer。
五、intern()的使用:
String.intern()源码如下:
如何将变量s 指向字符串常量池的数据呢:
方式一:String s = "junjie";//定义字面量的方式。
方式二:使用intern() 方法。
六、 new String("ab") 到底创建了几个对象:
答案:两个;
字节码如下:
ldc:加载常量池里的"ab", 堆空间 一个,所以两个。
拓展:new String("a")+new String("b") 创建了几个对象:
字节码如下:
对象一:new StringBuilder();
对象二:new String("a");
对象三:常量池里的"a"
对象四:new String("b")
对象五:常量池里的"b"
注意:StringBuilder.toString()方法:
字节码如下:
发现常量池里并没有创建对象,只是new String("ab");
对象六:new String("ab")
七、intern()方法的使用:jdk6 vs jdk7/8
例子:
jdk6 字符串常量池在方法区中,而jdk7/8 字符串常量池在堆中。
s3.intern() 在jdk6中,因为String s3 = new String("1")+new String ("1") 并没有在字符串常量池中创建“11”,而是在s3.intern() 之后在字符串常量池中创建了“11”,所以为fasle。
s3.intern() 在jdk7/8中,因为String s3 = new String("1")+new String ("1") 并没有在字符串常量池中创建“11”, jdk7/8将字符串常量池由方法区移动到了堆内存中,因为堆内存中已经创建了 "11" 对象,在s3.intern()之后,由于为了节省堆内存,只是在常量池中创建了新的引用指向了堆空间中的“11” 对象,所以为true。
总结:
注意:jdk7/8 是把堆内存对象的引用复制一份到常量池中,返回常量池中引用对象的地址。
八、intern()空间效率测试:
对于程序中大量存在的字符串,尤其其中存在很多重复字符串时,使用intern() 可以节省内存空间。
九、StringTable的垃圾回收:
JVM添加如下参数:
-Xms15m -Xmx15m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
PrintStringTableStatistics:统计字符串常量池的信息。
字符串常量池的统计信息:
字符串常量池的统计信息:
循环十万次呢:
字符串常量池的统计信息:
发现不够10万次,进行垃圾回收: