常量池与串池关系
举例:(老师这里讲的特别特别特别好!!)
// StringTable [ "a", "b" ,"ab" ] hashtable 结构,不能扩容
public class Demo {
// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 "a" 字符串对象
// ldc #3 会把 b 符号变为 "b" 字符串对象
// ldc #4 会把 ab 符号变为 "ab" 字符串对象
public static void main(String[] args) {
String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
System.out.println(s3 == s5);//false
String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab
System.out.println(s3 == s5);//true
}
}
StringTable的特性
1.常量池中的字符串仅为符号,第一次用到时才会变为对象,字符串延迟加载;
2.利用串池的机制,避免重复创建字符串对象;
3.字符串常量拼接的原理是编译期优化;
4.字符串变量拼接的原理是StringBuilder(1.8);
5.intern()可将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回。
下面的情况基于JDK1.8,intern()1.8不会拷贝
public class Demo1 {
// ["ab", "a", "b"]
public static void main(String[] args) {
String s = new String("a") + new String("b");
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
System.out.println( s2 == s); //true
System.out.println( s == "ab"); //true
}
}
public class Demo2 {
// ["ab", "a", "b"]
public static void main(String[] args) {
String x = "ab";
String s = new String("a") + new String("b");
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
System.out.println( s2 == x); //true
System.out.println( s == x ); //false
}
}
字符串相关面试题
public class Test {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern();
String x1 = "cd";
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 false/true
System.out.println(x1 == x2); //true
}
}
StringTable的位置
1.6 时在永久代的常量池中,永久代回收效率不高要到,垃圾回收触发的很晚(老年代);
1.7、1.8转移到堆中,垃圾回收的早;
实验报错情况
/**
* 演示 StringTable 位置
* 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
* 在jdk6下设置 -XX:MaxPermSize=10m
*/
public class Demo68 {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
int i = 0;
try {
for (int j = 0; j < 260000; j++) {
list.add(String.valueOf(j).intern());
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
}
GC overhead limit exceeded:98%的时间垃圾回收只达到2%的回收时报错。
StringTable垃圾回收
/**
* 演示 StringTable 垃圾回收
* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc(打印垃圾回收详细信息)
*/
public class DemoGC {
public static void main(String[] args) throws InterruptedException {
int i = 0;
try {
for (int j = 0; j < 100000; j++) { // j=100, j=10000
String.valueOf(j).intern();
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
}
StringTable性能调优
1.-XX:StringTableSize=桶个数,设置合适的参数,虽然参数较大会消耗一点内存;
参数太小会引起hash碰撞,查找字符串时比较消耗CPU,1.6起参数过小会自动rehash,但rehash前后bucket数量不变,不断的rehash也会消耗CPU,所以应当设置大小合理的参数。
2.考虑字符串对象是否入池,入池可以节约堆内存;
学习整理于解密JVM.