JVM OutOfMemoryError

Error的層級結構:

java.lang
類 OutOfMemoryError

java.lang.Object
  java.lang.Throwable
      java.lang.Error
          java.lang.VirtualMachineError
              java.lang.OutOfMemoryError
所有已實現的接口: 
Serializable 

 

API中的說明:因爲內存溢出或沒有可用的內存提供給垃圾回收器時,Java虛擬機無法分配一個對象,這時拋出該Error

需要理解JVM的內存劃分,堆(heep)和棧(stack)。

堆中存放Object對象數據,例如new出來的Object。當沒有任何引用指向某對象時,該對象可能被垃圾回收。有關垃圾回收算法,可參考其他有關文章,網上很多。關於對象引用,按強弱還有強引用,軟引用,弱引用,虛引用之分。強引用,一般不會被垃圾回收,若內存不足,只好拋出OutOfMemoryError;軟引用,垃圾回收器掃描時,若是內存不足的情況下,這種引用的對象會被回收。弱引用,只要垃圾回收器掃描,就會回收。

棧是動態的,每起一個線程就會起一個棧,每個線程中每個方法會在棧中起一個幀,稱棧幀;每一幀中存的是方法的局部變量,中間值等信息。方法執行結束,棧幀內存釋放,線程結束棧內存釋放。

下面的內容引用自51CTOevan2012的一篇文章:JVM的內存溢出異常

在Java虛擬機規範的描述中,除了PC(程序計數器)寄存器外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError的可能。當發生OutOfMemoryError時,無法用try...catch捕捉。

簡單介紹幾個JVM的參數設置:

-Xss128k

每個線程的java棧大小,一個線程java棧所有棧幀大小總和不能超過128k。

-Xms128m

表示JVM Heap(堆內存),初始分配128MB

-Xmx512m

表示JVM Heap(堆內存)最大允許的尺寸512MB。

-XX:PermSize=20M

設置方法區的初始大小

-XX:MaxPermSize=30M

設置方法區的最大值

關於這些參數的設置,詳細可以查看JVM性能調優方面的文章。

 

Java棧內存溢出

在Java虛擬機規範中,對這個區域規定了兩種異常狀況:StackOverflowError和OutOfMemoryError異常。

1.StackOverflowError異常

每當java程序代碼啓動一個新線程時,Java虛擬機都會爲它分配一個Java棧。Java棧以幀爲單位保存線程的運行狀態。當線程調用java方法時,虛擬機壓入一個新的棧幀到該線程的java棧中。只要這個方法還沒有返回,它就一直存在。如果線程的方法嵌套調用層次太多(如遞歸調用),隨着java棧中幀的逐漸增多,最終會由於該線程java棧中所有棧幀大小總和大於-Xss設置的值,而產生StackOverflowError內存溢出異常。例子如下:

package leon.java.outofmemory;

public class StackOverflow {
	private static int count = 0;

	public static void main(String[] args) {
		method();
	}

	public static void method() {
		System.out.println(++count);
		method();
	}

}

該代碼執行,到count到某個值時,拋出java.lang.StackOverflowError

隨着-Xss參數值的增大,可以嵌套的方法調用層次也相應增加。

綜上所述,StackOverflowError異常是由於方法調用的層次太深,最終導致爲某個線程分配的所有棧幀大小總和大於-Xss設置的值,從而發生StackOverflowError異常。

2.OutOfMemoryError異常

java程序代碼啓動一個新線程時,沒有足夠的內存空間爲該線程分配java棧(一個線程java棧的大小由-Xss參數確定),jvm則拋出OutOfMemoryError異常。例子如下:

package leon.java.outofmemory;

public class OutoffMemory {

	public static void main(String[] args) {
		int count = 0;
		while (true) {
			Thread thread = new Thread(new Runnable() {
				public void run() {
					while (true) {
						try {
							Thread.sleep(5000);
						} catch (Exception e) {	}
					}
				}
			});
			thread.start();
			System.out.println(++count);
		}
	}
}

當count達到某個值時發生:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

 

Java堆內存溢出

Java堆用於儲存對象實例。當需要爲對象實例分配內存,而堆的內存佔用又已經達到-Xmx設置的最大值。將會拋出OutOfMemoryError異常。例子如下:

package leon.java.outofmemory;

import java.util.ArrayList;
import java.util.List;

public class HeapOutoffMemory {

	public static void main(String[] args) {
		int count = 0;
		List<Object> list = new ArrayList<Object>();
		while (true) {
			list.add(new Object());
			System.out.println(++count);
		}
	}
}


count到某個值時,發生:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 

 

方法區溢出

方法區用於存放java類型的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。在類裝載器加載class文件到內存的過程中,虛擬機會提取其中的類型信息,並將這些信息存儲到方法區。當需要存儲類信息而方法區的內存佔用又已經達到-XX:MaxPermSize設置的最大值。將會拋出OutOfMemoryError異常。對於這種情況的測試,基本的思路是運行時產生大量的類去填滿方法區,直到溢出。這裏需要藉助cglib直接操作字節碼運行時,生成了大量的動態類。需要導入jarcglib-2.2.2.jarasm-3.3.jar,其他版本的也可以。例子如下:

package leon.java.outofmemory;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MaxPermSizeOverflow {
	public static void main(String[] args) {
		int count = 0;
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(MaxPermSizeOverflow.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				public Object intercept(Object obj, Method method,
						Object[] args, MethodProxy proxy) throws Throwable {
					return proxy.invoke(obj, args);
				}
			});
			enhancer.create();
			System.out.println(++count);
		}
	}
}


count達到某個值是,拋出:

5049

Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null

       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 leon.java.outofmemory.MaxPermSizeOverflow.main(MaxPermSizeOverflow.java:22)

Caused by: java.lang.reflect.InvocationTargetException

       at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)

       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

       at java.lang.reflect.Method.invoke(Method.java:597)

       at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384)

       at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219)

       ... 3 more

Caused by: java.lang.OutOfMemoryError: PermGen space

       at java.lang.ClassLoader.defineClass1(Native Method)

       at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)

       at java.lang.ClassLoader.defineClass(ClassLoader.java:616)

       ... 8 more

 


請注意提示信息:Caused by: java.lang.OutOfMemoryError: PermGen space

這就是方法區溢出。

瞭解了這個之後,對處理OutOfMemoryError的問題應該有很大的幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章