深入理解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
  • Java 虚拟机乱码 java虚拟机异常_字符串常量池

  • 异常信息如下:
  • Error occurred during initialization of VM
    OutOfMemoryError: Metaspace
  • 然后再看看Java6的,7是一样的,我们这里也不演示了

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 设置永久代空间大小,不加也行,如图:
  • Java 虚拟机乱码 java虚拟机异常_java_02

  • 再看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)

Java 虚拟机乱码 java虚拟机异常_OOM异常_03

  • 基于上面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了

Java 虚拟机乱码 java虚拟机异常_字符串常量池_04

JDK8

  • -Xms15m -Xmx15m,参数自己看情况调整:

5. 方法区异常

以后再慢慢说吧