NOTE

7.19 OOM问题举例

1. 为什么会发生OOM 得从GC回收过程说起(参考垃圾回收.md),可以看出GC之回收GCRoot达不到的对象的,GCRoot引用的对象是强引用,是没办法被回收的。 如果jvm内存中都是强引用对象,那么FullGC后jvm内存剩余空间很有可能不足以存放将要创建的新的对象,就会发生OOM 1.1.

Java创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 为什么会发生OOM

得从GC回收过程说起(参考垃圾回收.md),可以看出GC之回收GCRoot达不到的对象的,GCRoot引用的对象是强引用,是没办法被回收的。 如果jvm内存中都是强引用对象,那么FullGC后jvm内存剩余空间很有可能不足以存放将要创建的新的对象,就会发生OOM

1.1. OOM的可能原因

1.1.1. 代码问题【50%】

根据不同的分区进行分析:

  • 非堆内存
    • 堆外内存【直接内存】
    • 文件
    • socket
    • 数据库连接
  • 方法区

1.1.2. jvm参数配置问题【40%】

JVM参数调优.md

1.1.3. 是机器内存不够【10%】

升级硬件

2. 如何分析OOM

2.1. OutOfMemoryError【堆】

2.1.1. 代码

public class OOMTest
{
    //jvm参数:-Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\[user]\Downloads
    public static void main(String[] args)
    {
        Map<Integer, byte[]> map = new HashMap();
        for (int i = 0; i < 128; i++)
        {
            byte[] bytes = new byte[1024 * 1024];//1m
            map.put(i, bytes);
        }
    }
}

2.1.2. 获取内存Dump文件

  • 加上jvm参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
  • 使用jmap
jps -l //获取PID
jmap -dump:format=b,file=OOMTest.hprof PID

2.1.3. 使用MAT/JProfiler分析

2.1.3.1. MAT找到占用最大的空间的对象即可
  • 整体功能
  • 按照对象数量查看
  • 按照对象内存占用查看
  • MAT自动分析
2.1.3.2. JProfiler找到占用最大的空间的对象即可
  • 按照字节占用排序
  • 分析最大的对象
  • 查看什么对象引用了这个对象
  • 找到GCRoot

2.2. Metaspace【方法区】

2.2.1. 代码

//jvm参数:-XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=20m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\[user]\Downloads
public class OOMMetaspace
{
    static class OOMTest
    {
    }

    public static void metaOOM()
    {
        while (true)
        {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMTest.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor()
            {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
                {
                    return methodProxy.invokeSuper(o, null);
                }
            });
            enhancer.create();
        }
    }

    public static void main(String[] args)
    {
        metaOOM();
    }
}

2.2.2. 获取内存Dump文件

  • 加上jvm参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
  • 使用jmap
jps -l //获取PID
jmap -dump:format=b,file=OOMTest.hprof PID

2.2.3. 使用MAT/JProfiler分析

2.2.3.1. MAT
  • 搜索Class
  • 查看哪些引用了Class
  • 找到最多的引用
2.2.3.2. JProfiler
  • 按照ClassLoader分组

  • 查看谁引用了Class

  • 分析其中一个

  • 查看哪个GCRoot引用了他

  • 找到了GCRoot之后分析代码

2.3. 堆外内存

一次堆外内存泄露的排查过程-云栖社区-阿里云

3. 其他OOM举例

3.1. StackOverflowError【栈】

递归没有设置正确的终止条件

public class OOMStackOverFlow
{
    public static void stackOverflowError()
    {
        stackOverflowError();
    }

    public static void main(String[] args)
    {
        stackOverflowError();
    }
}

3.2. GC overhead limit exceeded【GC次数太多但是效率低】

分配的内存不够用,就是用大量的时间用于GC但是回收的效率很低。官方定义超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。

//-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails -XX:+PrintGCDateStamps
public class OOMGCOverHeadLimit
{
    public static void gcOverhead()
    {
        int i = 0;
        List<String> list = new ArrayList<>();

        try
        {
            while (true)
            {
                list.add(String.valueOf(i++).intern());
            }
        }catch (Throwable e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        gcOverhead();
    }
}

3.3. Direct buffer memory【直接内存】

//-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m -XX:+PrintGCDateStamps
public class OOMDirectBuffer
{
    public static void directBuffer()
    {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
    }

    public static void main(String[] args)
    {
        directBuffer();
    }
}

3.4. unable to create new native thread【线程】

public class OOMThreadTooMuch
{
    public static void thredTooMuch()
    {
        while (true)
        {
            new Thread(()->{
                try
                {
                    TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    public static void main(String[] args)
    {
        thredTooMuch();
    }
}