根據Java虛擬機規範,虛擬機記憶體中除過程式計數器之外的運行時數據區域都會發生OutOfMemoryError(OOM),本文將通過實際例子驗證分析各個數據區域OOM的情況。為了更貼近生產,本次所有例子都是通過調用介面觸發,並使用jvisualvm工具監控tomcat記憶體進行分析。 一、Java堆溢 ...
根據Java虛擬機規範,虛擬機記憶體中除過程式計數器之外的運行時數據區域都會發生OutOfMemoryError(OOM),本文將通過實際例子驗證分析各個數據區域OOM的情況。為了更貼近生產,本次所有例子都是通過調用介面觸發,並使用jvisualvm工具監控tomcat記憶體進行分析。
一、Java堆溢出
Java堆主要用於存儲對象和數組實例,只要不斷創建對象或者數組,並且保證CG Roots(垃圾收集器對象)到對象之間有可達路徑來避免垃圾回收機制清除這些對象,在對象數量到達最大堆的限制後就會產生記憶體溢出異常。
java代碼:
static class OOMObject{}; /** * 測試Java堆OOM */ private void testHeapOom(){ List<OOMObject> oomObjects=new ArrayList<>(); int count=0; boolean flag=true; while(flag){ try{ oomObjects.add(new OOMObject()); count++; }catch (Throwable throwable){ flag=false; System.out.println("count="+count); } } }
設置Jvm參數:在tomcat的 catalina.sh 中添加 JAVA_OPTS="$JAVA_OPTS -Xms64M -Xmx128M -XX:+HeapDumpOnOutOfMemoryError" 即初始堆記憶體為64M,最大堆記憶體為128M, -XX:+HeapDumpOnOutOfMemoryError 的含義是當虛擬機記憶體溢出後Dump出當前記憶體堆轉儲快照,這樣可以方便事後分析。
重啟tomcat後,設置jvisualvm連接,在JVM參數中可以看到剛纔添加的參數,表示設置成功:
切換到監控界面,在“堆”監控界面,可以看到堆大小穩定在64M,64即為我們設置的初始堆大小:
接下來,調用介面觸發Java堆OOM,同時監控tomcat日誌以及“堆”監控界面。
tomcat日誌:
tomcat日誌中很清楚地給出提示: java.lang.OutOfMemoryError: Java heap space (記憶體溢出異常:Java堆空間)。
jvisualvm監控:
從jvisualvm監控可以看到,不斷創建對象需要更多的對空間來存儲對象,當使用的堆到達設置的最大值128M的時候,就觸發了OOM。同時可以看到,在堆記憶體占用到達設置的最大堆記憶體之後,記憶體使用量又急劇下降,這是為什麼呢?這是因為當發生 java.lang.OutOfMemoryError: Java heap space 之後,Java虛擬機會對整個Java堆進行Full GC,Full GC使得堆使用量急劇下降。
二、虛擬機棧和本地方法棧溢出
由於在HotSpot中不區分虛擬機棧和本地方法棧,所以對於HotSpot來說,設置 -Xoss (設置本地方法棧大小)是無效的,棧容量只由 -Xss 參數設置。棧溢出測試代碼如下:
private int stackLength=1;
/**
* 測試棧溢出
*/
private void testStackLeak(){
try{
stackLength++;
testStackLeak();
}catch (Throwable e){
System.out.println("stackLength="+stackLength);
e.printStackTrace();
}
}
同時設置JVM參數: -Xss512k ,和第一步一樣,調用介面後觀察tomcat日誌:
可以看到,tomcat日誌列印出了 java.lang.StackOverflowError 的異常,並且棧的深度為2761。
接下來,將 -Xss 大小設置為1M,即設置JVM參數 -Xss1M ,調用介面後,再次觀察tomcat日誌:
可以看到,拋出 java.lang.StackOverflowError 異常的時候,棧的深度達到了7972。通過對比,我們可以很清楚地看到 -Xss 參數的作用:設置每個線程的棧大小,當線程請求的棧深度大於此設置的時候,就會出現 java.lang.StackOverflowError 異常。
三、方法區溢出
在HotSpot中,從jdk8開始,方法區的實現由永久代變更為元空間(MetaSpace),元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地記憶體。理論上取決於32位/64位系統可虛擬的記憶體大小,但也不是無限制的,可以配置參數來進行調整。現在通過代碼來進行測試分析:
(1)新建一個類 TestMetaSpaceClass ,註意該類不包含包名:
public class TestMetaSpaceClass { }
將該類編譯後的class文件放到 /mnt/class 路徑下;
(2)測試方法中不斷載入該類:
try { //準備url URL url = new File("/mnt/class").toURI().toURL(); URL[] urls = {url}; //獲取有關類型載入的JMX介面 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean(); //用於緩存類載入器 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); while (true) { //載入類型並緩存類載入器實例 ClassLoader classLoader = new URLClassLoader(urls); classLoaders.add(classLoader); classLoader.loadClass("TestMetaSpaceClass"); //顯示數量信息(共載入過的類型數目,當前還有效的類型數目,已經被卸載的類型數目) System.out.println("total: " + loadingBean.getTotalLoadedClassCount()); System.out.println("active: " + loadingBean.getLoadedClassCount()); System.out.println("unloaded: " + loadingBean.getUnloadedClassCount()); } } catch (Throwable e) { e.printStackTrace(); }
設置JVM參數 -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=32M ,調用介面後觀察tomcat日誌以及jvisualvm中Metaspace的變化曲線。
tomcat日誌:
通過tomcat日誌可以看到,拋出了 java.lang.OutOfMemoryError: Metaspace 的異常,此時總共載入的數目為5382
jvisualvm監控:
通過jvisualvm中Metaspace的監控也可以看到,隨著類的載入,元空間使用量逐漸增加,當超過最大值32M的時候,發生了OOM,接下來,將JVM參數再調大一點,設置為 -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=48M ,再次調用介面測試:
tomcat日誌:
jvisualvm監控:
從tomcat日誌和jvisualvm監控可以看出,當把參數 -XX:MaxMetaspaceSize 調大以後,可以載入更多的類。
以上分析如有錯誤之處,還請各位指正,謝謝!