前言 如今不管是在面試還是在我們的工作中,OOM總是不斷的出現在我們的視野中,所以我們有必要去瞭解一下導致OOM的原因以及一些基本的調整方法,大家可以通過下麵的事例來瞭解一下什麼樣的代碼會導致OOM,幫助我們以後在工作中能夠通過異常信息來判斷是JVM裡面哪個區域出現了問題。 先介紹一下筆者的相關編碼 ...
前言
如今不管是在面試還是在我們的工作中,OOM總是不斷的出現在我們的視野中,所以我們有必要去瞭解一下導致OOM的原因以及一些基本的調整方法,大家可以通過下麵的事例來瞭解一下什麼樣的代碼會導致OOM,幫助我們以後在工作中能夠通過異常信息來判斷是JVM裡面哪個區域出現了問題。
先介紹一下筆者的相關編碼環境。
jdk:java version "1.8.0_121"
ide:IntelliJ IDEA 2019.1 (Community Edition)
正文
1、Java堆溢出
Java中的堆存儲的都是對象實例,當我們不斷的創建對象,而GC的時候又不能回收,當存儲的對象大小超過了-Xmx的值,這時候則會出現OutOfMemoryError.[-XX:+HeapDumpOnOutOfMemoryError]參數可以讓jvm出現記憶體溢出的時候dump出記憶體堆轉儲快照。
/** * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError * @author wangzenghuang */ public class HeapOOMDemo { public static void main(String[] args) { List<String> stringList = new ArrayList<>(); while(true){ stringList.add("str"); } } }
運行結果,發生OOM,並且在我們項目的根目錄dump出當前的記憶體堆快照
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid1376.hprof ... Heap dump file created [7972183 bytes in 0.047 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at HeapOOMDemo.main(HeapOOMDemo.java:12) Process finished with exit code 1
簡單解決思路
那麼發生這個問題以後我們的解決思路有哪些呢?我們可以利用一些工具(例如Eclipse Memory Analyzer )來分析dump出的文件,一般來說,當生產環境發生OOM,比較常見的一個原因是發生了記憶體泄漏,用工具可以分析出泄露的對象到GC Root的引用鏈,從而定位到問題代碼。假如經過分析後發現記憶體中的對象都是“必須存活”的對象,這時候就要思考下項目中是否把“-Xms跟-Xmx”設置得太小了(當然這裡也不是隨意調大,需要結合機器的物理記憶體情況),再者需要留意代碼中是否有一些長生命周期的對象,從代碼中優化記憶體消耗。
2、方法區溢出
在jvm的方法區中,它主要存放了類的信息,常量,靜態變數等。在jdk8以前是通過“-XX:PermSize,-XX:MaxPermSize”來調整這個區域的值,但是從8開始呢,永久代的概念被MetaSpace(元空間)代替了,對應的參數也變成了“-XX:MetaspaceSize,-XX:MaxMetaspaceSize”。在這個例子中使用CGLib來動態生成一些類,方便我們實驗操作。
/** * VM Args: -XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=5m * @author wangzenghuang */ public class MethodAreaOOMDemo { public static void main(String[] args) { while(true){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(obj,objects); } }); enhancer.create(); } } static class OOMObject{} }
運行結果
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
簡單解決方法
這個問題的話,一般來說根據情況調整方法區的大小就行了,網上也有人說可以去掉MetaSpace的的大小限制,但是不建議這麼乾,畢竟不可控的事情我們要少點乾,很容易給自己埋雷。
3.棧溢出
對於我們來說,還有一個熟悉的錯誤,那就是“StackOverflowError”,它是由線程請求的棧深度超過了jvm允許的最大範圍而產生的。“-Xss”參數可以設置棧容量。
/** * VM Args: -Xss128k * @author wangzenghuang */ public class StackOFDemo { private static int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { StackOFDemo stackOFDemo = new StackOFDemo(); try { stackOFDemo.stackLeak(); }catch (Throwable e){ System.out.println("length : "+ stackLength); throw e; } } }
運行結果
length : 983 Exception in thread "main" java.lang.StackOverflowError at StackOFDemo.stackLeak(StackOFDemo.java:10) at StackOFDemo.stackLeak(StackOFDemo.java:10) ...
簡單解決思路
一般來說此類問題多出現在存在遞歸的地方,要從代碼里重新審視遞歸未結束的原因,若遞歸的方法沒問題可以根據實際情況調整“-Xss”參數的大小。還有一些代碼的循壞依賴也會造成此類情況,
4.直接記憶體溢出
本機直接記憶體預設與“-Xmx”設定的值一樣大,可以通過“-XX:MaxDirectMemorySize”修改。
/** * VM Args: -Xmx20m -XX:MaxDirectMemorySize=10 * @author wangzenghuang */ public class DirectMemoryOOMDemo { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalAccessException { Field field = Unsafe.class.getDeclaredFields()[0]; field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); while (true){ unsafe.allocateMemory(_1MB); } } }
運行結果
呃,一運行這段代碼idea直接閃退了,查閱其他資料可以得知當DirectMemory導致記憶體溢出時,Heap Dump文件是很小的,如果程式中有使用NIO的情況可以檢查一下。
總結
這裡所展示的代碼只是可以觸發jvm的各種錯誤,但是並不代表這是唯一的觸發錯誤的方方式,假如我們的代碼比較複雜,有時候遇到類似錯誤的時候還是需要耐心分析。
該文章首發於微信公眾號《深夜裡的程式猿》,轉載請註明出處,侵權必究。