1. 程式人生 > 實用技巧 >JVM隨筆記錄9-OOM記憶體溢位

JVM隨筆記錄9-OOM記憶體溢位

OOM記憶體溢位

1.metaspace記憶體溢位

  metaspace元空間是在jvm引數中是可以設定大小的,之前有講到元空間是存放類的一些資訊,那麼如果類資訊太多,元空間放不下,那是不是會先出現記憶體溢位的情況呢,而一旦元空間記憶體溢位,也就意味著JVM沒辦法再正常執行下去了,系統直接奔潰,那麼什麼情況下可能出現元空間記憶體溢位呢

  1.元空間設定太小,或者是使用的預設的metaspace引數,即根本沒有設定元空間大小,可能載入的類太多,導致OOM

  2.通過CGLIB動態建立類物件,如果程式碼沒有控制好,生成的類過多,那就可能把元空間填滿,最後導致元空間出現記憶體溢位問題

 以為模擬metaspace出現OOM的程式碼示例:

public class MetaspaceOOM {

    public static void main(String[] args) {
        int num = 0;
        while (true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Demo.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if(method.getName().equals("init")){ System.out.println("初始化之前====="); return methodProxy.invokeSuper(o, objects); }
else{ return methodProxy.invokeSuper(o, objects); } } }); Demo demo = (Demo)enhancer.create(); demo.init(); num++; System.out.println("建立了多少個子類:" + num); } } static class Demo{ public void init(){ System.out.println("初始化操作。。。。。。。。"); } } }

上述例項中設定的元空間大小為10M,-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M ,以下是執行結果:

可以看出在建立第250個字類的時候出現了java.lang.OutOfMemoryError: Metaspace,元空間記憶體溢位了。解決辦法可以是對enhancer做快取,不需要一直去建立新的類,比如:

public class MetaspaceOOM {

    static Demo demo = null;

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

        while (true){
            if(demo != null){
                demo.init();
            }else{
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Demo.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        if(method.getName().equals("init")){
                            System.out.println("初始化之前=====");
                            return methodProxy.invokeSuper(o, objects);
                        }else{
                            return methodProxy.invokeSuper(o, objects);
                        }
                    }
                });

                demo = (Demo)enhancer.create();
                demo.init();
                num++;
                System.out.println("建立了多少個子類:" + num);
            }
        }

    }


    static class Demo{
        public void init(){
            System.out.println("初始化操作。。。。。。。。");
        }
    }
}

2.執行緒的棧記憶體溢位

  之前文章有講到,jvm啟動後,類載入到記憶體中,執行緒訪問的時候都會有對應的虛擬機器棧,在訪問方法是會將方法對應的棧幀壓入棧中;另外我們在JVM引數中時可以對每個執行緒的虛擬機器棧的記憶體大小進行設定的,也就是引數-Xss,一般設定時1M,也就是說執行緒的虛擬機器棧記憶體是固定大小的,然後每個棧幀在虛擬機器棧中是會佔用記憶體的,當然棧幀出棧之後,這塊記憶體也會相應的釋放掉,那麼如果是虛擬機器棧中棧幀數量太多,把棧的空間佔滿了,那麼也就會出現記憶體溢位了,下面用程式碼模擬一個棧記憶體溢位的情況

public class StackOOM {
    public static int i = 0;
    public static void log(){
        i++;
        System.out.println("呼叫次數:" + i);
        log();
    }

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

執行後的結果:

從上面可以看出來,在遞迴呼叫了6923次之後,出現了java.lang.StackOverflowError棧記憶體溢位的情況。

3.堆記憶體溢位

  最後一種是堆記憶體溢位,堆分為新生代和老年代,eden區滿了之後會進行young gc,young gc之後存活的物件會進入survivor區,如果survivor區太小,放不下存活的物件,那麼這些物件就會進入到老年代,老年代滿了之後就會進行full gc,如果當前老年代的物件都還是存活的,都有被引用,那麼這些物件就無法被回收掉,如果這時候還有物件進入老年代,發現老年代已經沒有記憶體可以存放物件,那麼這個時候就會出現堆的記憶體溢位了,堆記憶體溢位是開發過程中最常見的一種記憶體溢位,出現堆記憶體溢位主要是以下兩種場景:

  1.突然的高併發請求,因為請求量過來,導致大量的存活物件進入到老年代,當要繼續放物件的時候,發現記憶體不夠出現記憶體溢位

  2.系統又記憶體洩漏的問題,建立了大量不必要的物件,同時還對些物件保持這引用,同樣把堆的空間沾滿後發生記憶體溢位

下面模擬一個堆記憶體溢位的場景

public class HeapOOM {

    public static void main(String[] args) {
        int i = 0;
        List<Object> list = new ArrayList<>();
        while (true){
            list.add(new Object());
            i++;
            System.out.println("建立了多少個物件:" + i);
        }
    }
}

上述給出的堆記憶體是2M,新生代1M,-Xms2M -Xmx2M -Xmn1M,執行結果如下:

可以看出在建立了47427個Object物件後堆記憶體溢位了。