转载自http://blog.csdn.net/xsf50717,做了一点修改补充。
参考:https://www.jianshu.com/p/2dc4c3dd5376
https://blog.csdn.net/xdugucc/article/details/78193805
1、String 常量池
为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。
Note:常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式。字符串常量池在方法区中(本文是jdk1.7前编辑的,jdk1.7之后字符串常量池移至堆中)
2、True OR False
首先得看下 几个对象???
相信很多 玩java的都做过类似 String s = new String(“abc”)这个语句创建了几个对象的题目。 这种题目主要就是为了考察对字符串对象的常量池掌握与否。其实上述的语句中是创建了2个对象:
第一个对象是"abc"字符串存储在常量池中
第二个对象在JAVA Heap中的 String 对象。这里不要混淆了s是放在栈里面的指向了Heap堆中的String对象
下面再看:
String str1 = new String(“abc”);
String str2 = “abc”;
这里是两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。运行时期创建
而第二种是先在栈中创建一个对String类的对象引用变量str2,然后通过符号引用去字符串常量池里找有没有"abc",如果没有,则将"abc"存放进字符串常量池,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。“abc”存于常量池在编译期间完成
还有几个经常考的面试题:
String s1 = new String(“s1”) ;
String s2 = new String(“s1”) ;
上面创建了几个String对象?
答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
String str1 = “abc”;
String str2 = “abc”;
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。
String str1 =new String (“abc”);
String str2 =new String (“abc”);
System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。
下面就是用几个例子说明来说明一下
例1
String s1 = "sss111";
String s2 = "sss111";
System.out.println(s1 == s2); //结果为tru
例2
String s1 = new String("sss111");
String s2 = "sss111";
System.out.println(s1 == s2); //结果为false
1.首先查看字符串常量池是否有字面量”sss111“,没有,则在常量池中创建,然后因为new,故在堆内存中开辟空间,创建”sss111“,然后直接返回其引用
2.第二个式子,查看常量池中是否有字面量”sss111“,发现有,则直接返回其引用
也就是说,一个是引用的常量池中的字符串,一个引用的是堆中字符串,故false
例3
String s0 = "111"; //pool
String s1 = new String("111"); //heap
final String s2 = "111"; //pool
String s3 = "sss111"; //pool
String s4 = "sss" + "111"; //pool
String s5 = "sss" + s0; //heap
String s6 = "sss" + s1; //heap
String s7 = "sss" + s2; //pool
String s8 = "sss" + s0; //heap
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //false
System.out.println(s3 == s7); //true
System.out.println(s5 == s6); //false
System.out.println(s5 == s8); //false
结果上面分析,总结如下:
- 单独使用""引号创建的字符串都是常量,编译期就已经确定存储到Constant Pool中;
- 使用new String(“”)创建的对象会存储到heap中,是运行期新创建的;
- 使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中,String pool中存有“aaaa”;但不会存有“aa”。
- 使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;只要s1是变量,不论s1指向池中的字符串对象还是堆中的字符串对象,运行期s1 + “aa”操作实际上是编译器创建了StringBuilder对象进行了append操作后通过toString()返回了一个字符串对象存在heap上。
- String s2 = “aa” + s1; String s3 = “aa” + s1; 这种情况,虽然s2,s3都是指向了使用包含变量的字符串连接符如"aa" + s1创建的存在堆上的对象,并且都是s1 + “aa”。但是却指向两个不同的对象,两行代码实际上在堆上new出了两个StringBuilder对象来进行append操作。在Thinking in java一书中285页的例子也可以说明。
- 对于final String s2 = “111”。s2是一个用final修饰的变量,在编译期已知,在运行s2+"aa"时直接用常量“111”来代替s2。所以s2+"aa"等效于“111”+ “aa”。在编译期就已经生成的字符串对象“111aa”存放在常量池中。
拓展:
String str4 = str3.intern()
intern()方法表示如果str3的字符串在池中有,就返回池中的引用,否则在池中创建一个引用,再返回该引用,因此str4 str1 str2指向的是同一个字符串对象。
JVM字符串常量池会进行垃圾回收吗?
会,但是对方法区的回收“成绩”比较难以令人满意,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾回收,而字符串常量池在方法区中。
假设一个字符串"everor"已经进入了常量池,但是当前系统中没有一个String对象引用常量池中的"everor"常量(即String str = new String(“everor”);),也没有其他地方引用了这个字面量(即String str = “everor”;),如果这个时候发生了内存回收,而且有必要的话,这个"everor"常量就会被系统清理出常量池。
在HotSpot虚拟机中,方法区又被称为永久代,永久代这个概念在其它虚拟机(如BEA JRockit、IBM J9等)中是不存在的。但是使用永久代来实现方法区,并不是一个好主意,因为这样更容易遇到内存溢出问题。从JDK1.7开始,逐步使用元空间(MetaSpace)代替永久代的概念。元空间的内存大小取决于本地内存大小。元空间代替永久代,并不意味着字符串常量池就到了元空间,而是移到了堆中。