运行时常量池是方法区(PermGen)的一部分。
需要提前了解:
1. JVM内存模型。
2. JAVA对象在JVM中内存分配
常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
- Java的自动装箱中其实就使用到了运行时常量池。详见:Java 自动装箱与拆箱的实现原理
- 还有字符串常量池。
字符串进入到常量池的两种方法:
1. new String()的实例调用intern()方法。
执行intern()方法时,若常量池中不存在等值的字符串,JVM就会在常量池中 创建一个等值的字符串,然后返回该字符串的引用。
2. “”(引号)引起来的内容(字面量)。
引号引起来的字符串,首先从常量池中查找是否存在此字符串,如果不存在则在常量池中添加此字符串对象,然后引用此字符串对象。如果存在,则直接引用此字符串。
重要提示:虚拟机启动时常量池中就存在“java”字符串实例,下面代码中s2调用intern()方法时,只是返回常量池中“java”实例的引用,而没有添加“java”实例。
先看下,下面的代码
public class Test {
public static void main(String[] args) {
String s1 = new StringBuilder().append("aa").append("bb").toString();
System.out.println(s1.intern() == s1);
String s2 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s2.intern() == s2);
}
}
输出结果如下:
JDK6
false
false
JDK7、JDK8
true
false
为什么不同版本的JDK输出的结果还不一样呢?带着疑问我们分别分析下JDK1.6 和JDK1.7-JDK1.8的常量池内存模型。
JDK1.6 常量池内存模型
Paste_Image.png如图中,我们可以看到常量池位于方法区(PermGen),常量池中引用的字符串实例也在常量池中。
- 通过调用intern()方法,会在常量池中生成一个相同字符串的对象
- “”内的字符串都会添加到常量池中,相当于引用的方法区中的字符串对象。
根据上图内存模型然后我们在分析下代码:
String s1 = new StringBuilder().append("aa").append("bb").toString();
System.out.println(s1.intern() == s1);
s1生成的对象在堆中,而s1.intern()的对象在常量池中,所以返回false。
String s2 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s2.intern() == s2);
s2生成的对象在堆中,而s2.intern()的对象也肯定在常量池中,所以也返回false。
JDK1.7-JDK1.8常量池内存模型
Paste_Image.png如图中,我们可以看到常量池位于方法区(PermGen),但常量池引用的字符串实例在堆中。(区别在这)
- 通过调用intern()方法,会在常量池添加一个此字符串实例的引用,(前提:常量池中没有相同内容的字符串)。
- “”内的字符串实例引用会添加到常量池中(前提:常量池中没有相同内容的字符串),如果常量池中存在,则引用常量池中的对象(防止重复创建对象)。
根据上图内存模型然后我们在分析下代码:
String s1 = new StringBuilder().append("aa").append("bb").toString();
System.out.println(s1.intern() == s1);
s1生成的对象在堆中,此时常量池中没有跟s1内容相同的字符串,所以在调用intern方法时,会在常量池中添加此对象的引用,所以返回为true。
String s2 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s2.intern() == s2);
s2生成的对象在堆中,而此时常量池中已经有一个跟s2内容相同的字符串常量,当s2调用intern方法时,返回常量池中已经存在的实例(相当于堆中有两个相同内容的实例:一个是new 出来的,一个是常量池中的)所以返回的结果为false。
“java”字符串实例为什么会在虚拟机启动时就存在?
请看下面的例子
public void meme(){
String s = new StringBuilder().append("me").append("me").toString();
System.out.println(s.intern()==s); //false
}
执行结果为false,说明“meme”也已经存在常量池中了。
常量池定义:用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
当类加载到内存后会在常量池中添加类、方法等符号的引用。方法名meme就会以字符串的方式加入到字符串常量池中,所以会感觉常量池中会内置很多字符串常量。
想了解更多精彩内容请关注我的公众号
网友评论
Area: HotSpot
Synopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
RFE: 6962931
[1] http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#changes
[2] http://ifeve.com/java-permgen-removed/
String s = new StringBuilder().append("me").append("me").toString();
System.out.println(s.intern()==s);
}
例如:你执行这个,meme也会在字符串常量池中存在的,类加载的时候,已经把方法名加载到常量池中了,还有好多其它地方的字符串也会加载到字符串常量池中。