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() { @Overridepublic 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物件後堆記憶體溢位了。