博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java方法区和运行时常量池溢出问题分析(转)
阅读量:5972 次
发布时间:2019-06-19

本文共 3256 字,大约阅读时间需要 10 分钟。

运行时常量池是方法区的一部分,方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

String.intern()是一个native方法,它的作用是:如果字符串常量池中已经包含了一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;

否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。
在JDK1.6及之前版本中,由于常量池分配在永久代中(即方法区),我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量,
注意,JDK1.7开始逐步开始“去永久代”。代码如下所示:

复制代码
package jvm;import java.util.ArrayList;import java.util.List;/* * VM Args: -XX:PermSize=10m -XX:MaxPermSize=10m */public class RuntimeConstantPoolOOM {    public static void main(String[] args) {        // 使用List保持着常量池引用,避免Full GC回收常量池行为        List
list = new ArrayList
(); int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } }}
复制代码

  注意,VM Args为配置VM的参数,在下图所示中配置:

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space    at java.lang.String.intern(Native Method)    at jvm.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:16)

  从运行结果中可以看到,运行时常量池溢出,在OutOfMemoryError后面跟随的提示信息是“PermGen space”,说明运行时常量池属于方法区(HotSpot虚拟机中的永久代)的一部分。但是使用JDK1.7运行这段程序不会得到相同的结果,而是出现以下的提示信息,这是因为这两个参数已经不在JDK1.7中使用了。

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10m; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10m; support was removed in 8.0

  如果在JDK1.7中运行RuntimeConstantPoolOOM.java程序,while循环将一直运行下去,但是,while循环并不是始终运行下去,直到系统中堆内存用完为止,一般需要过好长时间才会出现,不过笔者并没有在本地测试。因为在JDK1.7中常量池存储的不再是对象,而是对象引用,真正的对象是存储在堆中的。把RuntimeConstantPoolOOM.java运行时的VM参数改为如下所示:

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

运行程序后结果:

出现异常提示信息:java.lang.OutOfMemoryError: GC overhead limit exceeded,这里没有提示说堆还是持久代有问题,虚拟机只是告诉你你的程序花在垃圾回收上的时间太多了,却没有什么见效。默认的话,如果你98%的时间都花在GC上并且回收了才不到2%的空间的话,虚拟机才会抛这个异常。这是一个快速失败的安全保障的很好的实践。从运行结果中可以看出, 我们限定了堆的大小后,程序很快就运行异常了,异常信息和之前设想的一样,也就是常量池存储的不再是对象,而是对象引用,真正的对象是存储在堆中的。

关于JDK1.7字符串常量池的实现问题,这里还可以引申一个更有意义的影响,如以下代码所示:

 

public class StringInternStudyDemo {    public static void main(String[] args) {        printJdkVersion();        testAndPrintResult("计算机", "软件");        testAndPrintResult("ja", "va");        testAndPrintResult("ma", "in");    }    private static void testAndPrintResult(String prefix, String suffix) {        String str3 = new StringBuilder(prefix).append(suffix).toString();        System.out.println(str3.intern() == str3);    }    private static void printJdkVersion() {        String javaVersion = "java.version";        System.out.println(javaVersion + ":" + System.getProperty(javaVersion));    }}

 

JDK1.8下的执行结果:java.version:1.8.0_65truefalsefalse

 

JDK1.6下的执行结果:java.version:1.6.0_45falsefalsefalse

 

 

这段代码在JDK1.6中运行,会得到两个false,而在JDK1.7中运行,会得到一个true和一个false。

产生差异的原因是:
在JDK1.6中,intern()方法会把首次遇到的字符串复制到永久代中,返回的也是永久代中这个字符串的引用,而由StringBuilder创建的字符串实例在Java堆中,所以必然不是同一个引用,将返回false。
而JDK1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,而是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串是同一个。
对str2比较返回false,
是因为"java"字符串在执行StringBuilder()之前就已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。

如果在Hello.java中添加如下代码的话,返回的结果也是false,证明"main"字符串之前也出现过了。

String str3 = new StringBuilder("ma").append("in").toString();System.out.println(str3.intern() == str3);

 

参考

  1、《深入理解Java虚拟机》 2.4.3章节

  2、-技术小黑屋

  3、

  4、

http://www.cnblogs.com/luoxn28/p/5425425.html

 

你可能感兴趣的文章
【Linux】Linux 在线安装yum
查看>>
Atom 编辑器系列视频课程
查看>>
[原][osgearth]osgearthviewer读取earth文件,代码解析(earth文件读取的一帧)
查看>>
mybatis update返回值的意义
查看>>
expdp 详解及实例
查看>>
通过IP判断登录地址
查看>>
深入浅出JavaScript (五) 详解Document.write()方法
查看>>
Beta冲刺——day6
查看>>
在一个程序中调用另一个程序并且传输数据到选择屏幕执行这个程序
查看>>
代码生成工具Database2Sharp中增加视图的代码生成以及主从表界面生成功能
查看>>
关于在VS2005中编写DLL遇到 C4251 警告的解决办法
查看>>
提高信息安全意识对网络勒索病毒说不
查看>>
maya pyside 多个窗口实例 报错 解决
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
MVC中的三个模块
查看>>
Line: 220 - com/opensymphony/xwork2/spring/SpringObjectFactory.java:220:-1
查看>>
oracle 常用命令大汇总
查看>>
mysql 并行复制
查看>>
傲不可长,欲不可纵,乐不可极,志不可满——提高个人修养
查看>>