当前位置: 移动技术网 > IT编程>开发语言>Java > 荐 不能不知道的OOM异常分析以及解决方案

荐 不能不知道的OOM异常分析以及解决方案

2020年07月16日  | 移动技术网IT编程  | 我要评论

在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区都会发生OOM异常的可能,本文通过几个例子来了解一下虚拟机常见的OOM异常。

本文的代码参考《深入理解Java虚拟机(第二版)》

Java堆溢出

Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免避免垃圾回收清除对象,那么这些对象达到最大堆的容量限制之后就会产生内存溢出异常。

  • 设置VM参数,方便导出dump文件分析
-Xms20m  //JVM初始分配的内存20m
-Xmx20m  //JVM最大可用内存为20m
-XX:+HeapDumpOnOutOfMemoryError  //当JVM发生OOM时,自动生成DUMP文件
-XX:HeapDumpPath=/Users/wangchengming/Desktop/  //生成DUMP文件的路径,方便后面进行分析

如果不加-XX:HeapDumpPath-XX:+HeapDumpOnOutOfMemoryError参数的话,也可以通过jps命令找到pid,如下:
在这里插入图片描述
然后使用jmap -dump:format=b,file=serviceDump.dat 2513命令手打导出dump文件,如下:
在这里插入图片描述

  • 堆内存溢出代码
public class OOMTest {

    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> oomObjects = new ArrayList<>();
        while (true) {
            oomObjects.add(new OOMObject());
        }
    }
}
  • 运行结果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/wangchengming/Desktop/java_pid2493.hprof ...
Heap dump file created [27779830 bytes in 0.088 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at OOMTest.main(OOMTest.java:16)

Process finished with exit code 1
  • 使用JDK自带的VisualVM可视化工具导入dump文件

在这里插入图片描述
可以看出一共创建了810326个对象,占用了99.4%的内存,发现内存被撑爆了。

  • 解决思路
    • 通过上面的分析可以得出是堆内存溢出。这个时候可以定位到代码:OOMTest$OOMOject,发现是死循环导致的,修改代码之后就不会出现堆内存溢出了
    • 或者可以检查虚拟机堆参数(-Xmx和-Xms)是否可以调大
    • 思考是不是存在GC Roots不可达的对象,如果存在GC Roots不可达的对象则是内存泄漏
虚拟机栈和本地方法栈溢出

通常情况下栈容量是由-Xss参数设定。关于虚拟机栈和本地方法栈,在Java虚拟机中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,则会抛出StackOverflowError
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则会抛出OOM

接下来演示一波StackOverflowError

  • 设置VM参数-Xss160k,最小是160k,默认1M
  • 编写代码
private int stackLength = 1;

/**
 * 递归
 */
public void stackLeak() {
    stackLength++;
    stackLeak();
}

public static void main(String[] args) {
    OOMTest oomTest = new OOMTest();
    try {
        oomTest.stackLeak();
    } catch (Throwable e) {
        System.out.println("stack length : " + oomTest.stackLength);
        throw e;
    }
}
  • 从运行结果来看,单线程下虚拟机抛出的是StackOverflowError
stack length : 773
Exception in thread "main" java.lang.StackOverflowError
	at OOMTest.stackLeak(OOMTest.java:23)
	at OOMTest.stackLeak(OOMTest.java:24)
	at OOMTest.stackLeak(OOMTest.java:24)
	at OOMTest.stackLeak(OOMTest.java:24)

接下来演示一波OOM

  • 设置VM参数-Xss2M
  • 编写代码
private void dontStop() {
    while (true) {
    }
}

private void stackLeakByThread() {
    while (true) {
        Thread thread = new Thread(this::dontStop);
        thread.start();
    }
}

public static void main(String[] args) {
    OOMTest oomTest = new OOMTest();
    oomTest.stackLeakByThread();
}
  • 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
  • 解决思路
    • 通常在单线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出StackOverflowError 异常。如果是这种情况,就需要仔细检查代码有没有深度递归的情况。
    • 不断地建立线程的方式会导致内存溢出。如果是这种情况就要检查是不是有死循环,或者通过-Xss降低的每个线程栈大小的容量
方法区和运行时常量池溢出

方法区,(又叫永久代,JDK8后,元空间替换了永久代),用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。

  • 设置VM参数,-XX:MetaspaceSize=10M-XX:MaxMetaspaceSize=10M
  • 编写代码
public static void main(String[] args) {
    while (true) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OOMObject.class);
        enhancer.setUseCache(false);
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object obj, Method method,
                                    Object[] args, MethodProxy proxy) throws Throwable {
                return proxy.invokeSuper(obj, args);
            }
        });
        enhancer.create();
    }
}

static class OOMObject {
}
  • 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
	at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
	at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
	at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
	at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
	at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
	at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:387)
	at OOMTest.main(OOMTest.java:31)
  • 解决思路
    • 首先我们看到错误信息Caused by: java.lang.OutOfMemoryError: Metaspace,然后检查代码是否应用了大量的代理,发现确实是应用了代理。
    • 检查是否永久代空间设置得过小
    • 检查代码是否频繁错误得使用String.intern方法
    • 检查是否跟jsp有关。
    • 检查是否使用CGLib生成了大量的代理类
    • 重启大法,重启JVM
本机直接内存溢出

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使用,而且也可能导致OOM。

在JDK1.4 中新加入了NIO(New Input/Output)类,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定则默认和Java堆最大值(-Xmx指定)一致。

  • 设置VM参数-Xmx256m-XX:MaxDirectMemorySize=100M
  • 编写代码
public static void main(String[] args) throws IllegalAccessException, InterruptedException {
    //分配128MB直接内存
    ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);

    TimeUnit.SECONDS.sleep(10);
    System.out.println("ok");
}
  • 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
	at java.nio.Bits.reserveMemory(Bits.java:694)
	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
	at OOMTest.main(OOMTest.java:85)
  • 解决思路
    • 检查代码是否恰当
    • 检查JVM参数-Xmx-XX:MaxDirectMemorySize是否合理。
总结

通过以上的分析,一共得出以下几种常见OOM异常,希望可以帮到各位小伙伴

java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: Direct buffer memory

本文地址:https://blog.csdn.net/wangchengming1/article/details/107343013

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网