JVM的內(nèi)存溢出異常
在Java虛擬機(jī)規(guī)范的描述中,除了PC(程序計(jì)數(shù)器)寄存器外,虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)域都有發(fā)生OutOfMemoryError異常的可能。當(dāng)發(fā)生OutOfMemoryError異常時(shí),無(wú)法用try...catch捕捉。
在開始講解之前,在這里先簡(jiǎn)單介紹下虛擬機(jī)啟動(dòng)相關(guān)的一些內(nèi)存設(shè)置參數(shù)。因?yàn)镺utOfMemoryError異常發(fā)生,與這些參數(shù)的設(shè)置密切相關(guān)。
舉例說(shuō)明含義:
-Xss128k
每個(gè)線程的java棧大小,一個(gè)線程java棧所有棧幀大小總和***允許的尺寸128k。
-Xms128m
表示JVM Heap(堆內(nèi)存)最小尺寸128MB,初始分配
-Xmx512m
表示JVM Heap(堆內(nèi)存)***允許的尺寸256MB,按需分配。
-XX:PermSize=20M
設(shè)置方法區(qū)的初始大小
-XX:MaxPermSize=30M
設(shè)置方法區(qū)的***值
Java棧溢出
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:StackOverflowError和OutOfMemoryError異常。
1.StackOverflowError異常
每當(dāng)java程序代碼啟動(dòng)一個(gè)新線程時(shí),Java虛擬機(jī)都會(huì)為它分配一個(gè)Java棧。Java棧以幀為單位保存線程的運(yùn)行狀態(tài)。當(dāng)線程調(diào)用java方法時(shí),虛擬機(jī)壓入一個(gè)新的棧幀到該線程的java棧中。只要這個(gè)方法還沒(méi)有返回,它就一直存在。如果線程的方法嵌套調(diào)用層次太多(如遞歸調(diào)用),隨著java棧中幀的逐漸增多,最終會(huì)由于該線程java棧中所有棧幀大小總和大于-Xss設(shè)置的值,而產(chǎn)生StackOverflowError內(nèi)存溢出異常。例子如下:
- /**
- * VM Args: -Xss128k
- */
- public class Test {
- private int count = 0;
- public static void main(String[] args) {
- new Test().method();
- }
- public void method() {
- System.out.println(++count);
- method();
- }
- }
-Xss為128k。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到2312時(shí),發(fā)生如下異常:
- Exception in thread "main" java.lang.StackOverflowError
- at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:58)
- at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:392)
- at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:447)
- at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:544)
- at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:252)
- at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:106)
- at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
- at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
- at java.io.PrintStream.write(PrintStream.java:476)
- at java.io.PrintStream.print(PrintStream.java:547)
- at java.io.PrintStream.println(PrintStream.java:686)
- at jvm.Test.method(Test.java:17)
修改-Xss為1280k。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到26888時(shí),發(fā)生StackOverflowError異常。隨著-Xss參數(shù)值的增大,可以嵌套的方法調(diào)用層次也相應(yīng)增加。
綜上所述,StackOverflowError異常是由于方法調(diào)用的層次太深,最終導(dǎo)致為某個(gè)線程分配的所有棧幀大小總和大于-Xss設(shè)置的值,從而發(fā)生StackOverflowError異常。
2.OutOfMemoryError異常
java程序代碼啟動(dòng)一個(gè)新線程時(shí),沒(méi)有足夠的內(nèi)存空間為該線程分配java棧(一個(gè)線程java棧的大小由-Xss參數(shù)確定),jvm則拋出OutOfMemoryError異常。例子如下:
- /**
- * VM Args: -Xss128k
- */
- public class Test {
- 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);
- }
- }
- }
-Xss為128k。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到11887時(shí),發(fā)生如下異常:
- Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
- at java.lang.Thread.start0(Native Method)
- at java.lang.Thread.start(Thread.java:640)
- at jvm.Test.main(Test.java:20)
修改-Xss為1280k。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到1270時(shí),發(fā)生OutOfMemoryError異常。隨著-Xss參數(shù)值的增大,java程序可以創(chuàng)建的總線程數(shù)越少。
Java堆溢出
Java堆用于儲(chǔ)存對(duì)象實(shí)例。當(dāng)需要為對(duì)象實(shí)例分配內(nèi)存,而堆的內(nèi)存占用又已經(jīng)達(dá)到-Xmx設(shè)置的***值。將會(huì)拋出OutOfMemoryError異常。例子如下:
- /**
- * VM Args: -Xmx5m
- */
- public class Test {
- 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);
- }
- }
- }
-Xmx為5m。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到297868時(shí),發(fā)生如下異常:
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
- at java.util.Arrays.copyOf(Arrays.java:2760)
- at java.util.Arrays.copyOf(Arrays.java:2734)
- at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
- at java.util.ArrayList.add(ArrayList.java:351)
- at jvm.Test.main(Test.java:15)
修改-Xmx為10m。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到670205時(shí),發(fā)生OutOfMemoryError異常。隨著-Xmx參數(shù)值的增大,java堆中可以存儲(chǔ)的對(duì)象也越多。
方法區(qū)溢出
方法區(qū)用于存放java類型的相關(guān)信息,如類名、訪問(wèn)修飾符、常量池、字段描述、方法描述等。在類裝載器加載class文件到內(nèi)存的過(guò)程中,虛擬機(jī)會(huì)提取其中的類型信息,并將這些信息存儲(chǔ)到方法區(qū)。當(dāng)需要存儲(chǔ)類信息而方法區(qū)的內(nèi)存占用又已經(jīng)達(dá)到-XX:MaxPermSize設(shè)置的***值。將會(huì)拋出OutOfMemoryError異常。對(duì)于這種情況的測(cè)試,基本的思路是運(yùn)行時(shí)產(chǎn)生大量的類去填滿方法區(qū),直到溢出。這里需要借助CGLib直接操作字節(jié)碼運(yùn)行時(shí),生成了大量的動(dòng)態(tài)類。例子如下:
- /**
- * VM Args: -XX:MaxPermSize=50M
- */
- public class Test {
- public static void main(String[] args) {
- int count = 0;
- while (true) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(Test.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);
- }
- }
- }
-XX:MaxPermSize為50m。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到3953時(shí),發(fā)生如下異常:
- Caused by: java.lang.OutOfMemoryError: PermGen space
- at java.lang.ClassLoader.defineClass1(Native Method)
- at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
- at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
- ... 8 more
修改-XX:MaxPermSize為100m。其中的一次測(cè)試結(jié)果為,當(dāng)count的值累加到8022時(shí),發(fā)生OutOfMemoryError異常。隨著-XX:MaxPermSize參數(shù)值的增大,java方法區(qū)中可以存儲(chǔ)的類型數(shù)據(jù)也越多。
相關(guān)的參考資料:
1.深入Java虛擬機(jī)(原書第2版)
2.深入理解Java虛擬機(jī):JVM高級(jí)特性與***實(shí)踐
3.互聯(lián)網(wǎng)相關(guān)的文章
原文鏈接:http://www.cnblogs.com/evan2012/archive/2012/05/12/2497086.html
【編輯推薦】