那些年我们遇到的OOM

3次阅读

共计 4001 个字符,预计需要花费 11 分钟才能阅读完成。

JVM 内存模型

先聊聊 jvm 内存模型,在网上找到一张有直接内存的图片,方便后面讨论

这张图真是常看常新,今天我们从内存溢出的角度重新再审视一遍。
方法区,也称非堆,hotspot 中,1.7 叫 perm 区,1.8 叫元空间,因此这个区域溢出,1.7 就是 OutOfMemoryError: PermGen space,1.8 是 OutOfMemoryError: Metaspace

堆溢出 比较常见,OutOfMemoryError: Java heap space

虚拟机栈 的溢出是 StackOverflowError

本地方法栈 会出现 java.lang.OutOfMemoryError : unable to create new native Thread

直接内存 会抛出 OutOfMemoryError: Direct buffer memory

以下将逐个撑爆这些空间

先来撑爆虚拟机栈 StackOverflowError

不停地递归调用,jvm 不得不在虚拟机栈上分配栈帧空间,从而导致 sofe,感兴趣的还可以查看一下递归的次数,可能通过 -Xss 进行配置,通过命令 jinfo -flag ThreadStackSize [pid]可以查看栈空间大小的配置

public class StackOverflowErrorDemo {private static void test() {test();
    }

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

再来撑爆堆吧 OutOfMemoryError: Java heap space

先指定 -Xmx8m -Xms8m,然后直接在堆中生成一个 8m 字节的数组,可以直接看到效果

public class JavaHeapSpaceDemo {public static void main(String[] args) {byte[] bytes = new byte[8 * 1014 * 1024];
    }
}

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at com.meituan.waimai.jvm.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:11)

方法区有分说

如果是 1.7,会出现 PemGen space, 这要求我们不断往生成类的信息。由于 1.7,字符串常量池已经挪到堆中了,所以使用 String.intern()并不会导致 perm 区溢出

public class OOMDemo {public static void main(String[] args) {
        String str = "hello";
        List<String> list = Lists.newArrayList();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {str += (i + "").intern();
            list.add(str);
        }
    }
}

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 com.meituan.waimai.jvm.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:18)

那要怎么做呢,这里我们需要动态地生成一些类,直到把 perm 区撑爆,jvm 参数配置:-XX:MaxPermSize=8m

    static class OOMTest{}

    public static void main(final String[] args) {
        int i = 0;

        try {for (; ;) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return methodProxy.invokeSuper(o, args);
                    }
                });
                enhancer.create();}
        } catch (Throwable throwable) {throwable.printStackTrace();
        }
    }
}

Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
… 8 more

将 jdk 改为 1.8,-XX:MaxMetaspaceSize=20m 重新执行上述程序,结果变成 Metaspace 溢出

java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.meituan.waimai.jvm.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:36)

该对直接内存动手的

配置 jvm 参数,-XX:MaxDirectMemorySize=8m,然后分配 9m 的直接内存:

public class DirectMemoryDemo {public static void main(String[] args) {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(9 * 1024 * 1024);
    }
}

可以看到

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 com.meituan.waimai.jvm.DirectMemoryDemo.main(DirectMemoryDemo.java:13)

本地方法栈 unable to create new native Thread

不停地创建 java 线程,就可以把本地方法栈撑爆

public class NativeThreadDemo {public static void main(String[] args) {for (; ;) {new Thread(()->{
                try {TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }).start();}
    }
}

Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at com.meituan.waimai.jvm.NativeThreadDemo.main(NativeThreadDemo.java:21)

这个程序跑完忘了关,结果一会 mac 重启了,非常可怕。。。。
关于 unable to create new native Thread,知乎上有个非常深刻地讨论
https://www.zhihu.com/questio…

正文完
 0