JVM中OutOfMemoryError異常案例一方法區和直接記憶體
記憶體溢位之方法區和直接記憶體的介紹
通過實驗來介紹
方法區
和直接記憶體區
的OOM
一、方法區
注
:在JDK1.6 及以前版本中,由於常量池分配在永久代內,可以通過-XX:PermSize
和 -XX:MaxPermSize
限制方法區的大小,從而間接限制其中常量池的容量。 注意JDK1.8已經使用元空間
來代替永久區,所以在1.8中,這兩個引數將被忽略,而改使用-XX:MetaspaceSize
-XX:MaxMetaspaceSize
來控制元空間
案例一
案例描述: 執行時常量池導致的記憶體溢位異常
引數設定:
下面這個執行時常量池導致的記憶體溢位異常的案例 是JDK1.6
-XX:PermSize=10M -XX:MaxPermSize=10M
原始碼:
import java.util.ArrayList;
import java.util.List;
/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
* JDK1.6
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持著常量池引用,避免Full GC回收常量池行為
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer範圍內足夠產生OOM了
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
執行結果:
Exception in thread “main” java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
String.intern()
是一個Native
方法,它的作用是:如果字串 常量池這中已經包含此String物件的字串,則返回代表池中這個字串的String物件;否則,將此String物件包含的字串新增到常量池中,並且返回此String物件的引用。
注意:
使用JDK1.7
迴圈則將一直進行,得不到結果。 原因?
注
: JVM初始化也需要佔用一定的開銷。記憶體太小,在進行vm初始化時觸發GC
案例二:
案例描述: String.intern() 返回引用測試:
原始碼:
public class RuntimeConstantPoolOOM2 {
public static void main(String[] args) {
String str1 = new StringBuilder("中國").append("釣魚島").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}
結果:
- JDK1.6 : false false
- JDK1.7 : true false
結果分析:
- 在
JDK1.6
中,intern()
方法會把首次遇到
的字串例項複製到永久代
中,返回的也是永久代中這個字串例項的引用,而由StringBuilder
建立的字串例項在java堆
上,所以必然不是同一個引用。將返回false.
JDK1.7
中 的intern()
實現不會在複製例項,只是在常量池中記錄首次
出現的例項引用
。因此intern()
和 由StringBuilder
建立的那個字串例項是同一個。- 而str2 比較返回false是因為
java
這個字串在執行 StringBuilder.toString()之前出現過,字串常量池已經有它的引用了。不符合“首次出現” 的原則。而“中國釣魚島”字串則是首次出現。因此返回true.
案例三
如何讓方法區丟擲異常。下面的思路時執行時產生大量的類去填充方法區,導致溢位。
注: 當前很多框架,對類進行增強時,都會使用CGLib這個類位元組碼技術。
原始碼:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* JDK1.7以下執行,
*/
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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
執行結果:
Caused by: java.lang.OutOfMemoryError: PermGen space
...
---
JDK1.8
在JDK1.8中,需要將引數修改
-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
引數注意大小寫;和-XX:PermSize=10M -XX:MaxPermSize=10M 在1.8之前是相同效果
執行結果:
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
結果分析:
Metaspace(元空間)
記憶體溢位了。
二、直接記憶體
引數:
-XX:MaxDirectMemorySize
指定直接記憶體的大小,如果不指定,則預設和java堆最大值(-Xmx指定)一樣。
原始碼:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
結果:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at outofmemoryerror.DirectMemoryOOM.main(DirectMemoryOOM.java:19)
後記
(一)疑問:
- JDK1.8 元空間的大小限制是與什麼有關? 是與實體記憶體?
- 為什麼
JDK1.7
執行RuntimeConstantPoolOOM
這個類會始終迴圈而不結束(與intern
在不同版本中的實現有關?) -XX:MaxDirectMemorySize
指定直接記憶體的大小,如果不指定,則預設和java堆最大值(-Xmx指定)一樣。正確嗎?
(二)重點:
- 理解概念,兩種記憶體溢位是怎麼回事,掌握概念。不同版本執行結果可能不同,但是原理概念是相通。
- 理解
intern
這個方法
參考
- 《深入理解java虛擬機器》
注意: 不同版本的JDK設定相同的虛擬機器引數結果可能不一樣。二八原則
,不需要太過於專注細節。