读者可先参考:
深入解析String#intern
Java字符串常量池jdk1.8
《深入理解java虚拟机》String.intern()探究
Java常量池理解与总结
Java中的常量池(字符串常量池、class常量池和运行时常量池)
通过反编译深入理解Java String及intern
然后可对 String.intern 有一个大体地认识。
本篇中的 Java 例子使用的是 jdk1.8。
Java 中的常量池分静态常量池和运行时常量池。
静态常量池
编译后的 *.class 文件中除了有类、方法等描述信息以外,还有常量池,主要存放两大类常量:字面量(Literal)和符合引用量(Symbolic References)。字面量相当于 Java 层面的常量的概念,如文本字符串(数字)字面量,声明为 final 类型的常量等。
符号引用属于编译原理中的概念,包含类、方法的信息:
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
运行时常量池
JVM 在完成类加载以后,将类、方法等信息保存到元空间(jdk1.8 以前是方法区),会将 class 文件中的常量池存到堆中(jdk1.7 以前是存在方法区的运行时常量池中)。
运行时常量池相对于 class 文件常量池另外一个特性是具备动态性,并非预置入 class 文件中常量池的内容才能进入堆中的运行时常量池,运行期间也可以将新的常量放入池中,比如使用 String 类的 intern() 方法。
Java 示例
code1:
public class StringInternDemo {
public static void main(String[] args) {
test1();
}
public static void test1() {
String b = new String("flower");
String c = new StringBuilder().append("flow").append("er").toString();
System.out.println(c.intern() == c); // false
}
}
code2:
public class StringInternDemo0 {
public static void main(String[] args) {
test2();
}
public static void test2() {
String a = new String("flower" + "brown");
String c = new StringBuilder().append("flow").append("er").toString();
System.out.println(c.intern() == c); // true
}
}
为什么 code1 输出是 false,而 code2 是true 呢? code1 和 code2 的区别不大啊。前面说过 .class 文件中有常量池,在完成类加载以后会将类中的常量池保存到堆中,下面来看看 code1 和 code2 的字节码文件中的常量池。
code1:
code2:
从 code1 和 code2 的 class 常量池中存在 flower 和 flowerbrown。
来看图示:
code1:
在类加载完以后,堆中的常量池就存在 flower 了(网上很多文章说编译期就确定了),String b = new String("flower");
会创建两个对象,堆中创建一个,常量池中有一个(如果常量池中存在这个对象了,就不进行创建了)。明显,c.intern 返回的对象的地址和 c 本身所指向的地址是不一样的,输出 false。
code2:
String a = new String("flower" + "brown");
会在类加载后在常量池中确定 flowerbrown,此时常量池中是没有 flower 和 brown 的,所以这行代码 也只是创建两个对象,String:字符串常量池 这篇文章说会创建 4 个对象(flower、brown、flowerbrown、new String())其实是不对的。
c.intern() 发现常量池中并不存在 flower,则会在常量池中进行创建,但只是保存 flower 在堆中的引用,这个引用和 c 都是指向相同的地址,所以这里输出是 true。