Excel不相邻列如何打印在一起-英雄云拓展知识分享
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 运 行 时 常 量 池 导 致 的 内 存 溢 出 异 常
/★★
*VM Args:-XX:PermSize=6M -XX:MaxPermSize=6M
* @author zzm
*/
public class RuntimeConstantPool00M {
public static void main(String[]args) {
//使用Set保持着常量池引用,避免Ful1 GC回收常量池行为
Set//在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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~