ad

《深入理解 Java 虚拟机 JVM 高级特性与最佳实践(第3版)》_求知之路漫漫_2.4.3 方法区和运行时常量池溢出

网友投稿 114 2023-11-07

【摘要】 本书摘自《深入理解 Java 虚拟机 JVM 高级特性与最佳实践(第3版)》一书中第2章,第4节,周志明著。

2.4.3 方法区和运行时常量池溢出

由于运行时常量池是方法区的一部分,所以这两个区域的溢出测试可以放到一起进行。 前面曾经提到 HotSpot 从 JDK7 开始逐步“去永久代”的计划,并在JDK 8中完全使用元 空间来代替永久代的背景故事,在此我们就以测试代码来观察一下,使用“永久代”还是 “元空间”来实现方法区,对程序有什么实际的影响。

String:intern() 是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于 此 String 对象的字符串,则返回代表池中这个字符串的String 对象的引用;否则,会将 此 String 对象包含的字符串添加到常量池中,并且返回此 String 对象的引用。在 JDK 6或 更早之前的 HotSpot 虚拟机中,常量池都是分配在永久代中,我们可以通过-XX:PermSize 和-XX:MaxPermSize 限制永久代的大小,即可间接限制其中常量池的容量,具体实现如代 码清单2-7所示,请读者测试时首先以JDK6 来运行代码。

代 码 清 单 2 - 7 运 行 时 常 量 池 导 致 的 内 存 溢 出 异 常

《深入理解 Java 虚拟机 JVM 高级特性与最佳实践(第3版)》_求知之路漫漫_2.4.3 方法区和运行时常量池溢出

/★★

*VM Args:-XX:PermSize=6M -XX:MaxPermSize=6M

* @author zzm

*/

public class RuntimeConstantPool00M {

public static void main(String[]args) {

//使用Set保持着常量池引用,避免Ful1 GC回收常量池行为

Setset =new HashSet();

//在short 范围内足以让6MB的PermSize 产生OOM了

short i =0;

while(true) {

set.add(String.valueOf(i++).intern());

}

运行结果:

Exception in thread "main"java.lang.OutOfMemoryError:PermGen space

at java.lang.String.intern(Native Method)

at org.fenixsoft.oom.RuntimeConstantPool00M.main(RuntimeConstantPool00M.java:18)

从运行结果中可以看到,运行时常量池溢出时,在OutOfMemoryError 异常后面跟随的 提示信息是 “PermGen space”, 说明运行时常量池的确是属于方法区(即JDK6 的 HotSpot 虚拟机中的永久代)的一部分。

而使用JDK 7 或更高版本的JDK 来运行这段程序并不会得到相同的结果,无论是在 JDK7 中继续使用-XX:MaxPermSize参数或者在JDK8 及以上版本使用-XX:MaxMeta- spaceSize 参数把方法区容量同样限制在6MB, 也都不会重现 JDK 6中的溢出异常,循环将 一直进行下去,永不停歇°。出现这种变化,是因为自JDK 7起,原本存放在永久代的字符 串常量池被移至Java 堆之中,所以在JDK 7 及以上版本,限制方法区的容量对该测试用例 来说是毫无意义的。这时候使用-Xmx 参数限制最大堆到6MB 就能够看到以下两种运行结 果之一 ,具体取决于哪里的对象分配时产生了溢出:

//OOM异常一:

at java.base/java.lang.Integer.toString(Integer.java:440)

at java.base/java.lang.String.valueOf(String.java:3058)

at RuntimeConstantPool00M.main(RuntimeConstantPool00M.java:12)

//OOM 异常二:

Exception in thread "main"java.lang.OutOfMemoryError:Java heap space

at java.base/java.util.HashMap.resize(HashMap.java:699)

at java.base/java.util.HashMap.putVal(HashMap.java:658)

at java.base/java.util.HashMap.put(HashMap.java:607)

at java.base/java.util.HashSet.add(HashSet.java:220)

at RuntimeConstantPoo100M.main(RuntimeConstantPool00M.java from InputFile-

Object:14)

关于这个字符串常量池的实现在哪里出现问题,还可以引申出一些更有意思的影响,具 体见代码清单2-8 所示。

代 码 清 单2 - 8 S t r i n g . i n t e r n ( )返 回 引 用 的 测 试

public class RuntimeConstantPoolO0M {

public static void main(String[]args)(

mst.tllgl .append(“软件").toString();

String str2 =new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern()==str2);

这段代码在JDK6 中运行,会得到两个 false, 而 在JDK7 中运行,会得到一个 true 和 一 个 false 。 产生差异的原因是,在JDK 6中 ,intern() 方法会把首次遇到的字符串实例复 制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由 StringBuilder 创建的字符串对象实例在 Java 堆上,所以必然不可能是同一个引用,结果将 返回 false。

而JDK 7 (以及部分其他虚拟机,例如JRockit) 的 intern() 方法实现就不需要再拷贝字 符串的实例到永久代了,既然字符串常量池已经移到 Java 堆中,那只需要在常量池里记录 一下首次出现的实例引用即可,因此intern() 返回的引用和由StringBuilder 创建的那个字符 串实例就是同一个。而对str2 比较返回false, 这是因为 “java” 日这个字符串在执行 String-

Builder.toStringO 之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern() 方 法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回

true。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们 18664393530@aliyun.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《Python学习笔记 从入门到实战》_更了解Python的途径之一_9.2 装饰器:拓展函数功能
下一篇:《Python学习笔记 从入门到实战》_更了解Python的途径之一_7.2.2 类的基本用法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~

×