前言
本篇博文主要介紹使用程序觸發對應的內存溢出,並附帶上JVM常用的命令,供以後查看使用。
堆溢出
堆主要是用來存儲對象,我們只要不斷的創建對象,並防止虛擬機對對象進行回收則可以觸發堆溢出。
-Xms
設置堆最小值、-Xmx
設置堆最大值。如果兩者相同,則可以避免堆自動擴展;-XX:+HeapDumpOnOutOfMemoryError
可以讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便事後進行分析
/**
* Title:HeapOom 堆溢出
* Description: VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
* @author lin.xu
* @date 2017/12/4.
*/
public class HeapOom {
static class OomObject {
}
public static void main(String[] args) {
List<OomObject> list = new ArrayList<OomObject>();
while (true) {
list.add(new OomObject());
}
}
}
執行過後,會提示java.lang.OutOfMemoryError: Java heap space
。
解決方法:
通過內存映像分析工具(如Eclipse Memory Analyzer、JProfiler)對Dump出來的堆轉儲快照進行分析,重點先確定是內存泄漏還是內存溢出。如果是內存泄漏,可進一步通過工具查看泄漏對象到GC Roots的引用鏈,掌握泄漏對象的類型信息及GC Roots引用鏈信息就可以定位泄漏代碼位置;如果不是內存泄漏,則可以檢查
-Xms
和-Xmx
參數設置,另外查看下代碼判斷對象生命週期是否過長。
虛擬機棧和本地方法棧溢出
棧內存容量的計算由操作系統爲虛擬機分配的內存,扣除最大堆內存和方法區佔用的最大內存之後所剩餘的,由於程序計數器佔用內存小可以忽略不計。
-Xss
設置虛擬機棧大小,-Xoss
設置本地方法棧大小。但是對於HotSpot來說,-Xoss
是無效的,因爲HotSpot中根本不區分本地方法棧;
/**
* Title:JavaVmStackSof 棧溢出
* Description: -Xss180k
*
* @author lin.xu
* @date 2017/12/4.
*/
public class JavaVmStackSof {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVmStackSof jvss = new JavaVmStackSof();
try {
jvss.stackLeak();
} catch (Throwable e) {
System.out.println("Stack size: " + jvss.stackLength);
throw e;
}
}
}
執行過後,會提示java.lang.StackOverflowError
。
解決方法:
一般可以依據拋出的異常直接定位到代碼的位置。如果是由於過多線程導致的內存溢出,在不能減少線程數的情況下,只能減少最大堆和減少棧容量來換取更多的線程。
方法區和運行時常量池溢出
JDK1.7及之前版本,採用PermSize
及MaxPermSize
來設置方法區大小,由於JDK1.7開始已經在“去永久代”的實現,在JDK9中如果採用上述兩個配置運行本節的示例則直接提示
Ignoring option PermSize; support was removed in 8.0。
Ignoring option MaxPermSize; support was removed in 8.0
JDK9中需要將PermSize
替換爲MetaspaceSize
,MaxPermSize
替換爲MaxMetaspaceSize
。
方法區主要用於存放Class相關的信息,如類名、訪問修飾符、常量池、字段描述及方法描述等。測試時主要就是加載大量的類來使方法區溢出。
/**
* Title:JavaMethodAreaOom
* Description:vm args: -XX:PermSize=10M -XX:MaxPermSize=10M
*
* @author lin.xu
* @date 2017/12/5.
*/
public class JavaMethodAreaOom {
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 o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
}
執行過後,會提示java.lang.OutOfMemoryError: PermGen space
。
本機直接內存溢出
DirectMemory容量可以通過-XX: MaxDirectMemorySize
指定,如果不指定,則默認與Java堆最大值相同。
/**
* Title:DirectMemoryOom
* Description:vm args: -Xmx10M/-XX:MaxDirectMemorySize=10M
*
* @author lin.xu
* @date 2017/12/5.
*/
public class DirectMemoryOom {
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);
}
}
}
執行過後,會提示java.lang.OutOfMemoryError
。
由DirectMemory導致的內存溢出,明顯的特徵就是HeapDump文件中不會看見明顯的異常。如果發現OOM之後Dump文件很小,而程序中又間接或直接使用了NIO,則可以考慮檢查下是不是本機直接內存溢出問題。
JVM Args常用命令列表
命令 | 說明 |
---|---|
-Xms20M |
設置Java堆最小內存爲20M,必須以M爲單位 |
-Xmx20M |
設置Java堆最大內存爲20M,必須以M爲單位。如果值與-Xms 設置的相同,則Java堆不能擴展 |
-Xss128k |
設置JVM棧內存大小爲128K。在JDK9中,最小必須設置爲180k |
-Xoss128k |
設置本地方法棧大小爲128k。不過在HotSpot虛擬機中,本參數無效。因爲HotSpot中不區分JVM棧和本地方法棧 |
-XX:PermSize=10M |
JVM初始分配的永久代容量,必須以M爲單位。JDK8過後已無效。 |
-XX:MaxPermSize=10M |
JVM允許分配的永久代最大容量,必須以M爲單位。JDK8過後已無效 |
-Xnoclassgc |
關閉JVM對類的垃圾回收 |
-XX:+TraceClassLoading |
查看類的加載信息 |
-XX:+TraceClassUnLoading |
查看類的卸載信息 |
-XX:NewRatio=4 |
年輕代:老年代=1:4 |
-XX:SurvivorRatio=8 |
2個Survivor:1個Eden=2:8 |
-Xmn20M |
年輕代大小爲20M |
-XX:+HeapDumpOnOutOfMemoryError |
程序拋出異常時,存儲堆內存轉儲快照 |
-XX:+UseG1GC |
JVM使用G1垃圾收集器 |
-XX:+PrintGCDetails |
在控制檯上打印出GC具體細節 |
-XX:+PrintGC |
在控制檯上打印出GC信息 |
-XX:PretenureSizeThreshold=3145728 |
對象大於3145728(3M)時直接進入老年代分配,這裏只能以字節作爲單位 |
-XX:MaxTenuringThreshold=1 |
對象年齡大於1,則自動進入老年代 |
-XX:CompileThreshold=1000 |
當一個方法被調用1000次之後,被認爲是熱點代碼,觸發即時編譯 |
-XX:+PrintHeapAtGC |
查看每次GC前後堆內存佈局 |
-XX:+/-UseTLAB |
虛擬機是否啓動TLAB |
-XX:+PrintTLAB |
查看TLAB的使用情況 |
-XX:+UseSpining |
開啓自旋鎖 |
-XX:+PreBlockSpin |
更改自旋鎖的自旋次數,使用這個參數必須先開啓自旋鎖 |