寫程式碼實現棧溢位、堆溢位、永久代溢位、直接記憶體溢位
- 棧溢位(StackOverflowError)
- 堆溢位(OutOfMemoryError:Java heap space)
- 永久代溢位(OutOfMemoryError: PermGen space)
- 直接記憶體溢位
一、堆溢位
建立物件時如果沒有可以分配的堆記憶體,JVM就會丟擲OutOfMemoryError:java heap space異常。
堆溢位例項:
/** * 堆溢位 */ /** * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */ public static void main(String[] args) { List<byte[]> list = new ArrayList<>(); int i=0; while(true){ list.add(new byte[5*1024*1024]); System.out.println("分配次數:"+(++i)); } }
二、棧溢位
棧空間不足時,需要分下面兩種情況處理:
執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError
虛擬機器在擴充套件棧深度時無法申請到足夠的記憶體空間,將丟擲OutOfMemberError
附:當前大部分的虛擬機器棧都是可動態擴充套件的。
1、棧空間不足——StackOverflowError例項
/** * 棧溢位 */ public class Stack{ public static void main(String[] args){ new Stack().test(); } public void test(){ test(); } }
2、棧空間不足——OutOfMemberError例項
單執行緒情況下,不論是棧幀太大還是虛擬機器棧容量太小,都會丟擲StackOverflowError,導致單執行緒情境下模擬棧記憶體溢位不是很容易,不過通過不斷的建立執行緒倒是可以產生記憶體溢位異常。
public class StackSOFTest { int depth = 0; public void sofMethod(){ depth ++ ; sofMethod(); } public static void main(String[] args) { StackSOFTest test = null; try { test = new StackSOFTest(); test.sofMethod(); } finally { System.out.println("遞迴次數:"+test.depth); } } } 執行結果: 遞迴次數:982 Exception in thread "main" java.lang.StackOverflowError at com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:8) at com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:9) at com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:9) ……後續堆疊資訊省略
三、永久代溢位
永久代溢位可以分為兩種情況,第一種是常量池溢位,第二種是方法區溢位。
1、永久代溢位——常量池溢位
要模擬常量池溢位,可以使用String物件的intern()方法。如果常量池包含一個此String物件的字串,就返回代表這個字串的String物件,否則將String物件包含的字串新增到常量池中。
public class ConstantPoolOOMTest {
/**
* VM Args:-XX:PermSize=10m -XX:MaxPermSize=10m
* @param args
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i=1;
try {
while(true){
list.add(UUID.randomUUID().toString().intern());
i++;
}
} finally {
System.out.println("執行次數:"+i);
}
}
}
因為在JDK1.7中,當常量池中沒有該字串時,JDK7的intern()方法的實現不再是在常量池中建立與此String內容相同的字串,而改為在常量池中記錄Java Heap中首次出現的該字串的引用,並返回該引用。
簡單來說,就是物件實際儲存在堆上面,所以,讓上面的程式碼一直執行下去,最終會產生堆記憶體溢位。
下面我將堆記憶體設定為:-Xms5m -Xmx5m,執行上面的程式碼,執行結果如下:
執行次數:58162
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.Long.toUnsignedString(Unknown Source)
at java.lang.Long.toHexString(Unknown Source)
at java.util.UUID.digits(Unknown Source)
at java.util.UUID.toString(Unknown Source)
at com.ghs.test.ConstantPoolOOMTest.main(ConstantPoolOOMTest.java:18)
2、永久代溢位——方法區溢位
方法區存放Class的相關資訊,下面藉助CGLib直接操作位元組碼,生成大量的動態類。
public class MethodAreaOOMTest {
public static void main(String[] args) {
int i=0;
try {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
i++;
}
} finally{
System.out.println("執行次數:"+i);
}
}
static class OOMObject{
}
}
執行結果:
執行次數:56
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
四、直接記憶體溢位
DirectMemory可以通過-XX:MaxDirectMemorySize指定,如果不指定,預設與Java堆的最大值(-Xmx指定)一樣。
NIO會使用到直接記憶體,你可以通過NIO來模擬,在下面的例子中,跳過NIO,直接使用UnSafe來分配直接記憶體。
public class DirectMemoryOOMTest {
/**
* VM Args:-Xms20m -Xmx20m -XX:MaxDirectMemorySize=10m
* @param args
*/
public static void main(String[] args) {
int i=0;
try {
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while(true){
unsafe.allocateMemory(1024*1024);
i++;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("分配次數:"+i);
}
}
}
執行結果:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.ghs.test.DirectMemoryOOMTest.main(DirectMemoryOOMTest.java:20)
分配次數:27953
總結:
棧記憶體溢位:程式所要求的棧深度過大。
堆記憶體溢位: 分清記憶體洩露還是 記憶體容量不足。洩露則看物件如何被 GC Root 引用,不足則通過調大-Xms,-Xmx引數。
永久代溢位:Class物件未被釋放,Class物件佔用資訊過多,有過多的Class物件。
直接記憶體溢位:系統哪些地方會使用直接記憶體。
參考: