学习JVM必看的书籍无疑是《深入理解Java虚拟机》这本书了,在书中,关于运行时数据区域模型是这样描述的:
在这里我们只针对HotSpot VM来说,它是OracleJDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。在JDK7之前,这样的模型是正确的。但是到了JDK8,如图标红的部分,做了一些优化。
由于常量池具备动态性,在程序运行过程中会有大量的字符串常量在运行时常量池里产生,此时如果放在永久代,则无法恰当的设定永久代的大小,容易出现性能问题和内存溢出。下面一个例子证明在JDK8中,字符串常量池已经放在堆中:
String.intern()方法的作用是返回一个字符串引用,引用的是字符串常量池中的字符串(字面量),我们先来验证一下这个方法:
public class StringConstantsPoolTest { public static void main(String[] args) { String str = "abc"; // str存储在常量池 String str2 = new String("abc"); // str2 存储在堆中 System.out.println(str == str2); // 结果为false ,堆中的引用并不等于常量池中的引用 str2 = str2.intern(); // 获取str2在常量池中的引用 System.out.println(str == str2); } } 复制代码
结果如下:
证明 String.intern()方法返回了一个在常量池中的引用。 下面验证字符串常量池在堆中:
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); for (int i = 0; i < 100000000; i++) { for (int j = 0; j < 1000000; j++) { list.add(String.valueOf(i + j / 1000000).intern()); } } } 复制代码
结果如下:
我们看到这时报的是Java堆空间内存溢出,说明字符串常量池是在堆中,注意,此时仅仅是字符串常量池转移到了堆中,但是 运行时常量池依旧还是在方法区里