Java方法区和运行时常量池溢出问题分析(转)
运行时常量池是方法区的一部分,方法区用于存放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<String> list = new ArrayList<String>();
- 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.0
- Java 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_65
- true
- false
- false
- JDK1.6下的执行结果:
- java.version:1.6.0_45
- false
- false
- false
这段代码在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、Java中的字符串常量池-技术小黑屋
http://www.cnblogs.com/luoxn28/p/5425425.html
Java方法区和运行时常量池溢出问题分析(转)的更多相关文章
- Java方法区和运行时常量池溢出问题分析
运行时常量池是方法区的一部分,方法区用于存放Class的相关信息,如类名.访问修饰符.常量池.字段描述.方法描述等. String.intern()是一个native方法,它的作用是:如果字符串常量池 ...
- java常量池-字符串常量池、class常量池和运行时常量池
原文链接:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/ 在java的内存分配中,经常听到很多关于常量 ...
- Java中的常量池(字符串常量池、class常量池和运行时常量池)
转载. https://blog.csdn.net/zm13007310400/article/details/77534349 简介: 这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的 ...
- Java方法区的理解
方法区逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩. 但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap,目的就是要和堆分开 所以方法区看作是一块 ...
- java基础(八) 深入解析常量池与装拆箱机制
引言 本文将介绍常量池 与 装箱拆箱机制,之所以将两者合在一起介绍,是因为网上不少文章在谈到常量池时,将包装类的缓存机制,java常量池,不加区别地混在一起讨论,更有甚者完全将这两者视为一个整体, ...
- Java 字符串常量存放在堆内存还是JAVA方法区?
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池. JDK1.8开始,取消了Java方法区,取而代之的是位于直接内 ...
- Java编译时常量和运行时常量
Java编译时常量和运行时常量 编译期常量指的就是程序在编译时就能确定这个常量的具体值. 非编译期常量就是程序在运行时才能确定常量的值,因此也称为运行时常量. 在Java中,编译期常量指的是用fina ...
- (4)java方法区
java方法区[名词解析] --->和java堆一样,方法区是一块所有线程共享的内存区域. --->保存系统的类信息,比如,类的字段,方法,常量池等. ...
- Java方法区和永久代
Java方法区和永久代 目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9. JRockit是oracle发明的,用于其WebLogic服务器,IBM JVM是IBM ...
随机推荐
- Handler.removeMessages的作用,有时候为什么一定要先remove一下呢
removeMessages会将handler对应message queue里的消息清空,如果带了int参数则是对应的消息清空.队列里面没有消息则handler会不工作,但不表示handler会停止. ...
- AV_百度百科
AV_百度百科 AV(影片门类)
- 6.6.2 自己主动泛型化(automatic generalization)
6.6.2 自己主动泛型化(automatic generalization) 在这一章,我们已经实现了几个 F# 的高阶函数.也看到了在 F# 和 C# 中并排的实现.F# 实现的非常重要方面,是我 ...
- Swift学习笔记十三:继承
一个类能够继承(inherit)还有一个类的方法(methods),属性(property)和其他特性 一.基本的语法 class Human{ var name :String init(){ na ...
- Laravel创建Model
它已被用于CI框架.最近学习使用Laravel框架,要总结一些遇到的问题是一个创纪录,供以后调用.此外,我希望能够碰到同样的问题的朋友的帮助. 在Laravel数据库表是根据Laravel写好的程序去 ...
- jqGrid笔记@简单实现
jqGrid在MVC中的版本是已经通过 HtmlHelper 的扩展方法封装后的产物,webForm版本貌似也将其封装成了服务器端空间,所以我推荐下载原生的jqGrid版本的地址为:http://ww ...
- 为经典版eclipse增加web and JavaEE插件
链接地址:http://jingyan.baidu.com/article/f25ef2546cd0e2482d1b825d.html 为经典版eclipse增加web and JavaEE插件 百度 ...
- Java基础06 组合
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们已经尝试去定义类.定义类,就是新建了一种类型(type).有了类,我们接着构造 ...
- _splitpath / _wsplitpath 将绝对路径分割为盘符、路径、文件名、扩展名。
今天分享下一个路径分割的API,可以将一个完整的绝对路径分割为: 盘符(包括冒号:) 路径(包含前面&后面的\,不含盘符&文件名) 文件名(不含扩展名) 扩展名(包含前面的.) 先不说 ...
- QT的动态翻译功能,可能依赖于消息(事件)机制
QTranslator translator; bool b = translator.load(QString(":/qm/lang_en"));以后,无论使用QObject的t ...