首先需要知道一个概念就是:jdk1.6的时候字符串常量池是放在运行时数据区的方法区中的,jdk1.6之后字符串常量池是放在堆中的,这个改动会导致字符串方法intern结果的不同。
intern本质是重用String对象,减少内存消耗,具体代码如下:
static int max = 100000;
static String[] arr = new String[max];
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < max; i++) {
arr[i] = new String(String.valueOf(1000)).intern();
//arr[i] = new String(String.valueOf(1000));
}
long end = System.currentTimeMillis() - start;
System.out.println(end + "s");
System.gc();
}
对象生成情况如下图:
很明显第一种情况gc之后只有常量池中的那个新生成的对象,第二种情况强引用没办法gc故有10W个重复的且没必要的字符串对象。
注:这里使用了MAT插件,在run configurations中vm参数设置为-agentlib:hprof=heap=dump,format=b即可在运行结束后生成hprof文件
intern放在在jdk1.6于jdk1.6以上之后的区别:jdk1.6中是如果常量池中没有该对象那么复制一个新的对象放入常量池中,并返回常量池中该对象的地址,而jdk1.6以后是如果常量池中没有该对象那么将当前对象的引用放入到常量池中,而不在生产新的对象放入常量池。如果常量池中已经存在对象,调用intern方法后就会返回常量池中的对象,在这种情况下没有区别。
基于jdk1.6
String s = new String("abc"); // 一开始常量池是空的,会产生两个对象,一个是在常量池中,一个是在堆中
String s1 = "abc"; //检查常量池,发现常量池中已经存在,则直接返回常量池中的对象
String s2 = new String("abc"); // 常量池中已经存在abc对象故不会在常量池中继续生产对象,因为是new操作所以在堆区中还是会产生新的对象
System.out.println(s == s1); // 一个是堆区中的对象,一个是常量池中的对象 返回false
System.out.println(s == s2); // 两个都是new操作都会生产新的对象,==比较的是地址,所以是false
System.out.println(s1 == s2); // 一个是常量池中的对象,一个是堆中的对象,返回false
System.out.println(s == s.intern()); // s指向堆中的对象,s.intern返回常量池中的对象,false
System.out.println(s1 == s1.intern()); // s1指向常量池中的对象,s1.intern返回的也是常量池中的对象,true
System.out.println(s.intern() == s2.intern()); // 都返回常量池中的同一个对象abc,结果true
String hello = "hello";
String hel = "hel";
String lo = "lo";
System.out.println(hello == "hel" + "lo"); // 返回true,字面值相加会直接合并,相同与hello == "hello";
System.out.println(hello == "hel" + lo); //返回false,"hel" + lo会在堆区生成新的对象
String是一个常量(常量即指值不能被改变)一旦创建后不能被改变
String类是用final修饰的故不能被继承