1. 程式人生 > 程式設計 >那些年,我們遇到的OOM

那些年,我們遇到的OOM

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.(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,知乎上有個非常深刻地討論 www.zhihu.com/question/64…