java字符串、常量池和intern方法理解
1 对于常量字符串,在编译的时候就会将这个字符存入常量池
常量池中其实有一个table表对象维护所有的字符串
2 对于 new String("abc")的理解:
有种说法new String会在堆中和常量池中创建两个对象,这种说法其实没错
因为括号里面出现的"abc"其实相当于String s1 = "abc"的作用,这个对象就已经存入了常量池
但是如果出现new String("a") + new String("b") + new String("c") 这种,那么常量池只有a b c 三个对象,
但是并没有abc这个对象。对于 new String(str1); 这个时候str1的所代表的对象是不会存入常量池的,而是存入堆中
3 对于 String test = “a” + “b” + “c”:
这个时候常量池里面其实只有一个对象,因为 a b c 它们在编译的时候就会生成一个abc字符串,
所以只有一个对象被创建反编译过后成了 String test = “abc”:
同理:String s = new String("abc" + "def");反编译成为了 String s = new String("abcdef");也就是创建了两个对象
但是:String s = new String("abc" + str); 就不一样了
4 对于变量字符串参与+运算
String s = “abc” + str;
因为编译期间无法确认str,所以不能以常量来计算,这个时候就得使用new来操作
但是如果 str 是 final 类型,那么又不一样了,他就是一个常量了,以后代码中出现的str都会被
它的字符串来代替
5 常量池保存的是字符串还是字符串的引用?
其实回答这个问题需要区分jdk版本
jdk<=6 : 那么常量池中肯定保存的都是字符串
jdk>=7 : 那么常量池中保存可以是字符串,也可以是引用
那什么时候会出现保存引用呢?
这就需要了解jdk7以后,常量池被移到堆中,并且intern方法机制有改变,这个方法
在jdk7以前,机制是看常量池中有没有指定的字符串,如果没有则复制一份字符串到
常量池中;jdk7以后,区别是如果常量池没有则会把堆中的字符串的引用复制一份到
常量池中。
所以字符串可以保存在常量池中,也可以保存在堆中;
jdk7以后,常量池中可以保存字符串,也可以保存字符串的引用。
其实从图2 就可以得出一些结论:
a 字符串其实可以存储在两个地方,池和堆中都有存储,否则也不会出现intern这个方法去
常量池中找有没有这个字符串,那说明有的字符串根本就不在池中
比如 new String(String.valueOf(123))
new String("a") + new String("b")
创建的对象都是在堆中
6 对于String.intern方法(这个方法只有new对象才能调用)
jdk<7:则是将字符串本身在常量池中查找,如果有,则返回常量池中的引用,
没有则在常量池中新建字符串本身,然后返回常量池的引用
jdk>=7:则是将字符串本身在常量池中查找,如果有,则返回常量池中的引用,这里没有变化,
如果没有则会将堆中的引用传递给常量池,然后返回这个指向堆的引用
7 对于不同的版本的jdk常量池的位置(方法区也被叫做永久带)
jdk6:常量池放在方法区(永久带)中
jdk7:常量池放在堆中,方法区中的其余的还在方法区(永久带)
jdk8+:常量池放在堆中,方法区(永久带)被取消,被放在元空间
可以使用jdk8测试:
jvm参数:-Xmx20m -Xms20m -XX:-UseGCOverheadLimit
这里的-XX:-UseGCOverheadLimit是关闭GC占用时间过长时会报的异常
运行下面代码:
public static void main(String[] args) throws Throwable {
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
异常信息是:java.lang.OutOfMemoryError: Java heap space
不得不承认,我基本不转载,因为想自己消化,然后总结,记录笔记
但是一定要学会拿来主义:参考 https://blog.****.net/baidu_31657889/article/details/52315902