天天看點

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

java虛拟機規範規定的java虛拟機記憶體其實就是java虛拟機運作時資料區,其架構如下:

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

<img width="492" height="325" src="file:///C:/Users/zpy/AppData/Local/Temp/msohtml1/01/clip_image001.jpg" <="" span="">‘ v:shapes="_x0000_i1029">

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

Java虛拟機棧,本地方法棧和程式計數器是線程隔離的資料區。

Java官方定義:

Java各記憶體區域分析:

通過分析各個區域的内容我們分别寫出各個區域的記憶體溢出執行個體

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

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

public

class HeapLeak {

    public

static void main(String[] args){

        ArrayList list = newArrayList();

        while(true){

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

        }

    }

    static

class method{

}

從Java官方API中我們知道,棧中存儲:基本資料類型,對象引用,方法等。下面以無限遞歸建立方法和申請棧空間為例,分别示範棧的stackOverflow和OutOfMemory

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

packageMemory;

class StackLeak {

        method();

static void

method(){

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

class StackOutOfMemory {

static int count = 1;

    public 

void noStop() {

        while (true) {

void newThread() {

            Thread t = new Thread(new Runnable() {

                publicvoid run() {

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

                    noStop();

                }

            });

            t.start();

        new StackOutOfMemory().newThread();

Java hotspot虛拟機中一個線程占用記憶體可通過-Xss設定,而能建立的線程數計算方法為:

可建立線程數=(實體記憶體-Os預留記憶體-堆記憶體-方法區記憶體)/單個線程大小

在測試的時候這裡還有點小插曲,電腦強關了一次,因為把-Xss設定成了2M,記憶體使用增加到97%左右,作業系統死了,這個程序不斷在建立線程,但是并沒有因為記憶體不足而停下來,直到電腦完全死掉也沒有報出錯誤資訊。最後分析是因為電腦空閑記憶體還有600M,線上程還沒有建立完的時候,已經開啟的線程太多,在死之前大概能開到200多個,對記憶體大量消耗,造成系統挂掉。

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

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

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

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

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

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

下面以不斷添加Stirng為例:

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

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

Java記憶體溢出執行個體總結堆溢出棧溢出常量池溢出方法區溢出本機直接記憶體溢出

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

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

importjava.util.ArrayList;

class ConstantPoolLeak {

static void main(String[] args) {

        int count = 0;

        while (true)

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

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

Java的反射和動态代理可以動态産生Class,另外第三方的CGLIB可以直接操作位元組碼,也可以動态産生Class,下面通過CGLIB來示範。

importjava.lang.reflect.Method;

class MethodAreaLeak {

        public

        Enhancer enhancer =new

Enhancer();

        enhancer.setSuperClass(OOMObject.class);

        enhancer.setUseCache(false);

        enhancer.setCallback(newMethodInterceptor(){

        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類(Unsafe的getUnsafe()方法隻有啟動類加載器Bootstrap才能傳回執行個體)直接操作本機直接記憶體。通過使用-XX:MaxDirectMemorySize=10M,限制最大可使用的本機直接記憶體大小為10MB,例子代碼如下

importjava.lang.reflect.Field;

class DirectMemoryOOM {

    private

static final

int _1MB = 1024 * 1024 * 1024;

static void main(String[] args)throws Exception {

        Field unsafeField = Unsafe.class.getDeclaredFields()[0];

        unsafeField.setAccessible(true);

        Unsafe unsafe = (Unsafe) unsafeField.get(null);

            // unsafe直接想作業系統申請記憶體

            unsafe.allocateMemory(_1MB);