深入理解Java虚拟机——模拟几种常见的OOM异常
- 1. 模拟方法区异常
- 2. 字符串常量池引起的异常
- 3. 关于 GC overhead limit exceeded
- 4. java.lang.OutOfMemoryError: Java heap space
- JDK6
- JDK8
- 5. 方法区异常
- 关于内存的文章推荐
1. 模拟方法区异常
- 因为程序在启动的时候会加载很多类,所以这么模拟是很简单的,我们只需要把初始值和最大值两个参数调小一下即可,不如设置1m 和 2m试试,然后启动一个空的main方法即可模拟。
- 首先,我们先试一下Java8的,如图
-XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=2m
- 异常信息如下:
- 然后再看看Java6的,7是一样的,我们这里也不演示了
Error occurred during initialization of VM
OutOfMemoryError: Metaspace
2. 字符串常量池引起的异常
- 因为字符串常量池的位置根据JDK版本的不同有所改变,所以我们模拟两个JDK6和JDK8两个版本的,关于JDK版本中常量池的变化,可以看这篇文章:深入理解Java虚拟机——关于方法区的变化 + JVM简单内存变化图.
- 代码很简单,用了String 的
intern()
方法,如下:
public class StringOom {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
while (true){
list.add(UUID.randomUUID().toString().intern());
}
}
}
- 先看
JDK6
的运行结果:
模拟6时,最好加参数-XX:PermSize=10m -XX:MaxPermSize=10m
设置永久代空间大小,不加也行,如图: - 再看
JDK8
的运行结果:
模拟JDK8时,就得用参数控制堆内存大小了,因为6的时候字符串常量池在方法区,而方法区的实现是永久代,永久代初始内存步算太大;但是8时字符串常量池在堆中,不控制内存大小很难模拟,所以我们加上参数:-Xms5m -Xmx5m
,(注意:这里如果遇到GC overhead limit exceeded
问题了,往下看),此处我填的最终参数是-Xms5m -Xmx5m -XX:-UseGCOverheadLimit
。
但是需要注意的是,这短代码在1.8下的OOM我个人觉得不是因为字符串常量池满了导致的异常,而是整堆满了,因为加不加intern()
方法都是一样的,而且字符串常量池之所以从永久代移到堆里,一个主要的原因就是:也是为了避免永久代容易产生的OOM,移到堆里之后很难因为字符串常量池而导致OOM异常,强调一下:这一段仅限个人理解,有解释的不恰当的地方还希望各位阅读大佬指出!!
但是无论怎么调整参数的值错误提示都是下面这个,如:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at java.util.UUID.toString(UUID.java:380)
at com.liu.susu.jvm.strings.strings.StringOom.main(StringOom.java:19)
- 基于上面jdk8的问题,想想还是不对,按理来应该有多种情况的异常呀,好了,不试了,出于好奇又翻阅了一下周大大的秘籍,直接截个图吧:
- 上面是周大大的代码,要不换个代码测试测试?真的挺无聊的,如果不是强迫症又不好奇还是别测试了,真的有点无聊,好了再好奇一次吧也无聊一次吧
① 参数是-Xms5m -Xmx5m -XX:-UseGCOverheadLimit
时,异常如图:
② 把参数调大,参数是-Xms15m -Xmx15m -XX:-UseGCOverheadLimit
时,异常如图:
总之,就是具体取决于哪里的对象分配时产生了溢出,反正测试结果就放这了,自己体会体会吧。
好了,不玩了,玩烦了~!
3. 关于 GC overhead limit exceeded
- 关于
java.lang.OutOfMemoryError: GC overhead limit exceeded
的OOM,是超出了GC开销限制。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。导致异常的原因:是因为堆内存太小,没有足够的内存。 - 在上面的例子《字符串常量池引起的异常》中用JDK8事,如果把内存调到很小会报出这个问题。不妨借用上面的例子再试试一试:
- 如果要想解决这个错误提示,用这个参数即可:
-XX:-UseGCOverheadLimit
,但是需要注意的是,内存还是不够的,只是不报上面的提示而已:
参数设置:-Xms1m -Xmx1m -XX:-UseGCOverheadLimit
对于此问题,1.6是一样的,不多说了。
4. java.lang.OutOfMemoryError: Java heap space
JDK6
- 还是同一个例子,我们如果稍微把代码做一个改变,即:去掉
intern()
方法,如下:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
while (true){
// list.add(UUID.randomUUID().toString().intern());
list.add(UUID.randomUUID().toString());
}
}
- 然后输入参数:
-XX:PermSize=10m -XX:MaxPermSize=10m -Xms12m -Xmx12m
,这个参数大小自己看情况而调整 - 也可以用我们熟悉的代码模拟例子,更方便理解:
public static void main(String[] args) {
Map<Integer,Object> map = new HashMap<Integer, Object>();
int i =0;
while (true){
Object object = new Object();
map.put(i++,object);
}
}
这个应该没啥可说的了,一直new对象,我们说new出来的都放在堆上,所以当堆空间不够用的时候就OOM了
JDK8
-Xms15m -Xmx15m
,参数自己看情况调整:
5. 方法区异常
以后再慢慢说吧