java内存区域
可以参考《关于线程安全问题》的第一节内容
字符串常量池的2种使用方法:
- 字面量使用
- String.intern()方法
两种创建字符串对象的方式的分析
字面量
String s1 = "abc";
String s2 = "abc";
分析:
创建了几个对象?
采用字面值的方式创建对象,首先会去字符串常量池查找"abc",如果存在,则返回索引给s1;如果不存在,则在字符串常量池创建对象,返回这个对象的索引给s1。
附上
new关键字
String s3 = new String("xyz");
String s4 = new String("xyz");
分析:
创建了几个对象?
采用new的方式创建对象,首先会去字符串常量池查找"xyz",如果存在,不创建;如果不存在,则在字符串常量池创建对象,接着在堆中创建"xyz",返回堆中的索引。
附上
String.intern()方法
在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用
而JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可
几个例子
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
分析:
jdk6和jdk7各是什么结果?
jdk6和jdk7这方面的区别就是字符串常量池由永久代搬到了堆。影响到intern()实现不同。
"计算机","软件","ja","va",这几个字面量会在常量池中创建。"计算机软件","java"会在堆中创建。
执行intern()
- 1.6"计算机软件"会复制一份到常量池,所以2个地址不等;"java"由于常量池已经存在,直接返回首次放入的地址。
- 1.7"计算机软件"直接保存堆中的地址,所以2个地址相等;"java"由于常量池已经存在,直接返回首次放入的地址。
经典例子
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
//System.out.println(s1 == s2); // true
//System.out.println(s1 == s3);// true
// System.out.println(s1 == s4); // false
// System.out.println(s1 == s9); // false
// System.out.println(s4 == s5); // false
// System.out.println(s1 == s6); // true
分析:
第一行先去字符串常量池查找有无"Hello",没有创建一个对象,返回索引给s1,有直接返回给s1;
第二行先去字符串常量池查找有无"Hello",发现有了,直接返回给s2;
第三行编译器会直接优化成"Hello",同第二行;
第四行先去字符串常量池查找有无"Hel"、"lo",没有创建对象,接着在堆上创建对象返回索引给s4;
第五行先去字符串常量池查找有无"Hello",发现有了,不创建,在堆上创建一个新的对象;
第六行取s5字符串到字符串常量池中找,发现有了,返回索引给s6
第七八行先去字符串常量池查找有无"H"、ello,没有创建对象,返回索引给s7、s8,有直接返回给s7、s8;
第九行编译器优化成(new StringBuilder()).append(var7).append(var8).toString();显然堆中创建了对象
附上
反编译字节码后的代码
总结
看了上面介绍的内容,对一些关于字符串的引用创建判断的问题都能迎刃而解了。(^-^)V
参考资料: