- 機構級別:普通會員
- 信用等級:
資料認證
未通過身份證認證
未通過辦學許可認證
- 學校瀏覽人次:次
- 加盟時間:2017年03月10日
西安尚學堂java:Java常見內存溢出異常與代碼實現
Java堆是用來存儲對象實例的,因此如果我們不斷地創建對象,并且保證GC Root和創建的對象之間有可達路徑以免對象被垃圾回收,那么當創建的對象過多時,會導致heap內存不足,進而引發OutOfMemoryError異常.本文西安尚學堂java培訓專家為大家整理了Java常見內存溢出異常與代碼實現:
/**
* @author xiongyongshun
* VM Args: java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*/
public class OutOfMemoryErrorTest {
public static void main(String[] args) {
List list = new ArrayList<>();
int i = 0;
while (true) {
list.add(i++);
}
}
}
上面是一個引發OutOfMemoryError異常的代碼,我們可以看到,它就是通過不斷地創建對象,并將對象保存在list中防止其被垃圾回收,因此當對象過多時,就會使堆內存溢出.
通過java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError我們設置了堆內存為10兆,并且使用參數-XX:+HeapDumpOnOutOfMemoryError讓JVM在發生OutOfMemoryError異常時打印出當前的內存快照以便于后續分析.
編譯運行上述代碼后,會有如下輸出:
>>> java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError com.test.OutOfMemoryErrorTest16-10-02 23:35
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1810.hprof ...
Heap dump file created [14212861 bytes in 0.125 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 com.test.OutOfMemoryErrorTest.main(OutOfMemoryErrorTest.java:15)
Java棧StackOverflowError
我們知道, JVM的運行時數據區中有一個叫做虛擬機棧的內存區域,此區域的作用是:每個方法在執行時都會創建一個棧幀,用于存儲局部變量表,操作數棧,方法出口等信息.
因此我們可以創建一個無限遞歸的遞歸調用,當遞歸深度過大時,就會耗盡棧空間,進而導致了StackOverflowError異常.
下面是具體的代碼:
/**
* @author xiongyongshun
* VM Args: java -Xss64k
*/
public class OutOfMemoryErrorTest {
public static void main(String[] args) {
stackOutOfMemoryError(1);
}public static void stackOutOfMemoryError(int depth) {
depth++;
stackOutOfMemoryError(depth);
}
}
當編譯運行上述的代碼后,會輸出如下異常信息:
Exception in thread "main" java.lang.StackOverflowError
at com.test.OutOfMemoryErrorTest.stackOutOfMemoryError(OutOfMemoryErrorTest.java:27)
方法區內存溢出
注意,因為JDK8已經移除了永久代,取而代之的是metaspace,因此在JDK8中,下面兩個例子都不會導致java.lang.OutOfMemoryError: PermGen space異常.
運行時常量池溢出
在Java 1.6以及之前的HotSpot JVM版本時,有永久代的概念,即GC的分代收集機制是擴展至方法區的.在方法區中,有一部分內存是用于存儲常量池,因此如果代碼中常量過多時,就會耗盡常量池內存,進而導致內存溢出.
那么如何添加大量的常量到常量池呢?這時就需要依靠String.intern()方法了. String.intern()方法的作用是:若此String的值在常量池中已存在,則這個方法返回常量池中對應字符串的引用;反之將此String所包含的值添加到常量池中,并返回此String對象的引用.在JDK 1.6以及之前的版本中,常量池分配在永久代中,因此我們可以通過設置參數"-XX:PermSize"和"-XX:MaxPermSize"來間接限制常量池的大小.
注意,上面所說的String.intern()方法和常量池的內存分布僅僅針對于JDK 1.6及之前的版本,在JDK 1.7或以上的版本中,由于去除了永久代的概念,因此內存布局稍有不同.
下面是實現常量池內存溢出的代碼例子:
/**
* @author xiongyongshun
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstantPoolOOMTest {
public static void main(String[] args) {
List list = new ArrayList();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
我們看到,這個例子中,正是使用了String.intern()方法,向常量池中添加了大量的字符串常量,因而導致了常量池的內存溢出.
我們通過JDK1.6編譯并運行上面的代碼,會有如下輸出:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.test.RuntimeConstantPoolOOMTest.main(RuntimeConstantPoolOOMTest.java:16)
需要注意的是,如果通過JDK1.8來編譯運行上面代碼的話,會有如下警告,并且不會產生任何的異常:
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0
方法區的內存溢出
方法區作用是存放Class的相關信息,例如類名,類訪問修飾符,字段描述,方法描述等.因此如果方法區過小,而加載的類過多,就會造成方法區的內存溢出.
//VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
public class MethodAreaOOMTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MethodAreaOOMTest.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();
}
}
}
上面的代碼中,我們借助CGlib來動態地生成大量的類,在JDK6下,運行上面的代碼會產生OutOfMemoryError: PermGen space異常:
/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java -jar -XX:PermSize=10M -XX:MaxPermSize=10M target/Test-1.0-SNAPSHOT.jar
輸出結果如下:
Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:637)
at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
... 11 more
MetaSpace內存溢出
在方法區的內存溢出內存溢出一節中,我們提到, JDK8沒有了永久代的概念,因此那兩個例子在JDK8下沒有實現預期的效果.那么在JDK8下,是否有類似方法區內存溢出之類的錯誤呢?當然有的.在JDK8中,使用了MetaSpace的區域來存放Class的相關信息,因此當MetaSpace內存空間不足時,會拋出java.lang.OutOfMemoryError: Metaspace異常.
我們還是以上面提到的例子為例:
//VM Args: -XX:MaxMetaspaceSize=10M
public class MethodAreaOOMTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MethodAreaOOMTest.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();
}
}
}
此例子的代碼部分沒有改動,唯一的區別是我們需要使用JDK來運行這段代碼,并且設著參數-XX:MaxMetaspaceSize=10M,這個參數告訴JVM Metaspace的最大大小是10M.
接著我們使用JDK8來編譯運行這個例子,輸出如下異常:
>>> java -jar -XX:MaxMetaspaceSize=10M target/Test-1.0-SNAPSHOT.jar
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.test.MethodAreaOOMTest.main(MethodAreaOOMTest.java:22)