Java內存溢出實例總結

jopen 10年前發布 | 52K 次閱讀 Java內存溢出 Java開發

java 虛擬機規范規定的 java 虛擬機內存其實就是 java 虛擬機運行時數據區,其架構如 下:

' v:shapes="_x0000_i1029">

其中方法區和堆是由所有線程共享的數據區。

Java虛擬機棧,本地方法棧和程序計數器是線程隔離的數據區。

Java官方定義:http://www.98ki.com/servlet/HomeServlet?method=get&id=53

Java各內存區域分析:http://www.98ki.com/servlet/HomeServlet?method=get&id=43

通過分析各個區域的內容我們分別寫出各個區域的內存溢出實例

堆溢出

Java的官方文檔我們可以看出,Java堆中存放:對象、數組。下面以不斷創建對象為例:

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

 

public class HeapLeak {

    public static void main(String[] args){

        ArrayList list = new ArrayList();

        while(true){

            list.add(new HeapLeak.method());

        }

    }

    static class method{

    }

}

 

棧溢出

Java官方API中我們知道,棧中存儲:基本數據類型,對象引用,方法等。下面以無限遞歸創建方法和申請棧空間為例,分別演示棧的stackOverflowOutOfMemory

 

l     Exception in thread "main" java.lang.StackOverflowError

package Memory;

 

public class StackLeak {

    public static void main(String[] args){

        method();

    }

    public static void method(){

        method();

    }

}

 

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

package Memory;

 

public class StackOutOfMemory {

    public static int count = 1;

    public  void noStop() {

        while (true) {

        }

    }

 

    public  void newThread() {

        while (true) {

            Thread t = new Thread(new Runnable() {

                public void run() {

                    System.out.println("已創建第"+count+++"個線程");

                    noStop();

                }

            });

            t.start();

        }

 

    }

    public static void main(String[] args){

        new StackOutOfMemory().newThread();

       

    }

}

 

Java hotspot虛擬機中一個線程占用內存可通過-Xss設置,而能創建的線程數計算方法為:

可創建線程數=(物理內存-Os預留內存-堆內存-方法區內存)/單個線程大小

在測試的時候這里還有點小插曲,電腦強關了一次,因為把-Xss設置成了2M,內存使用增加到97%左右,操作系統死了,這個進程不斷在創建線程,但是并沒有因為內存不足而停下來,直到電腦完全死掉也沒有報出錯誤信息。最后分析是因為電腦空閑內存還有600M,在線程還沒有創建完的時候,已經開啟的線程太多,在死之前大概能開到200多個,對內存大量消耗,造成系統掛掉。

這里又出現一個有趣的現象,當線程順序創建到第88個的時候,count跳了很多,并且開始無序,有興趣的可以深入學習一下線程方面的問題,我也會在后面的博客分析這個問題。

而換成200M的時候,創建第二個線程的時候就報了OutOfMemory.不管Xss設置多少,報錯之后,程序都會一直走下去,執行已開線程中的任務。

Java內存溢出實例總結

常量池溢出

Java官方API中我們知道,常量區代表運行時每個class文件中的常量表。它包括幾種常量:編譯期的數字常量、方法或者域的引用(在運行時解析)。runtime constant pool的功能類似于傳統編程語言的符號表,盡管它包含的數據比典型的符號表要豐富的多。

下面以不斷添加Stirng為例:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

常量池在方法區中,首先設置持久代大小,使其不可擴展。

然后需要做的就不停地往方法區中加字符串。其中intern()就是查看方法區中有沒有這個字符串,沒有的話就加進去,如果這里不用intern(),字符串是存在堆里的,會報heapOutOfMemory.

這里需要注意的是,在HotSpot中,方法區是在堆的持久代中的。

package Memory;

 

import java.util.ArrayList;

 

public class ConstantPoolLeak {

    public static void main(String[] args) {

        int count = 0;

        ArrayList list = new ArrayList();

        while (true)

            list.add(String.valueOf(count++).intern());

    }

}

方法區溢出

Java官方API中我們知道,方法區存放每個Class的結構,比如說運行時常量池、域、方法數據、方法體、構造函數、包括類中的專用方法、實例初始化、接口初始化。

Java的反射和動態代理可以動態產生Class,另外第三方的CGLIB可以直接操作字節碼,也可以動態產生Class,下面通過CGLIB來演示。

import java.lang.reflect.Method;

 

public class MethodAreaLeak {

 

 

        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();

    }

    }

    class OOMObject{

    }

    }

本機直接內存溢出

Java虛擬機可以通過參數-XX:MaxDirectMemorySize設定本機直接內存可用大小,如果不指定,則默認與java堆內存大小相同。JDK中可以通過反射獲取Unsafe(UnsafegetUnsafe()方法只有啟動類加載器Bootstrap才能返回實例)直接操作本機直接內存。通過使用-XX:MaxDirectMemorySize=10M,限制最大可使用的本機直接內存大小為10MB,例子代碼如下

package Memory;

 

import java.lang.reflect.Field;

 

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 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直接想操作系統申請內存

            unsafe.allocateMemory(_1MB);

        }

    }

}

來自:http://www.98ki.com/blog/HomeServlet?method=get&id=93

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!