首頁  >  文章  >  Java  >  詳解JVM 各區域的用途及潛在出現異常的程式碼實例

詳解JVM 各區域的用途及潛在出現異常的程式碼實例

黄舟
黄舟原創
2017-03-20 10:56:191983瀏覽

程式計數器

用於給字節碼解釋器來選取嚇一跳需要執行的字節碼指令。每個執行緒有一個獨立的程式計數器去,且各個執行緒之間互不影響。如果執行緒正在執行一個Java方法,這個計數器記錄的是正在執行的虛擬機器字節碼指令的記憶體位址;如果執行的是Native方法。在計數器為Undefined。此區域是JVM規格中唯一一個不存在OOM的區域

虛擬機器堆疊(局部變數空間)

存放編譯器可知的各種基本資料型別(boolean、byte、char、 short、int、float、long、double)、物件應用(reference)。 64位元的double、long佔用2個槽。記憶體空間在編譯期間就能確定,當進入一個方式時,這個方法需要分配的局部變數空間是完全確定的,透過-Xss設定記憶體容量

#異常狀況:

StackOverflowError堆疊深度大於虛擬機所允許的深度

OOM 如果虛擬機棧可以動態擴展(目前大部分Java虛擬機都可以動態擴展,只不過Java虛擬機規範中的也允許固定長度的虛擬機堆疊),如果擴充功能是無法申請到足夠的記憶體

在單一執行緒下,無論是猶豫棧幀太大還是虛擬機棧容量太小,當記憶體無法分配的時候,虛擬機拋出的都是StackOverflowError

/**
* VM Args:-Xss128k
* 
* stack length:2402 Exception in thread "main" java.lang.StackOverflowError
*/
public class JavaVMStackSOF {

private int stackLength = 1;

public void stackLeak() {
    stackLength++;
    stackLeak();
}

public static void main(String[] args) throws Throwable {
    JavaVMStackSOF oom = new JavaVMStackSOF();
    try {
        oom.stackLeak();
    } catch (Throwable e) {
        System.out.println("stack length:" + oom.stackLength);
        throw e;
    }
}
}

如果測試時不限於單線程,通過不斷的建立線程的方式到是可以產生內存溢出的異常。但是這樣產生的記憶體溢出異常與佔空間是否足夠大並不存在任何联系,或者準確的說,在這種情況下,為每個線程的棧分配的內存越大,反而越容易產生內存溢出的異常。

因為作業系統分配給每個行程的記憶體是有限制的,譬如32的window限制為2GB。此測試是透過創建大量的線程。每個執行緒佔用棧記憶體分配大量的內存,導致系統沒有足夠的記憶體。使其無法自動擴充

/**
 * VM Args:-Xss2M (这时候不妨设大些)
 *
 * java.lang.OutOfMemoryError:unable to create new native thread
 */
public class JavaVMStackOOM {

       private void dontStop() {
              while (true) {
              }
       }

       public void stackLeakByThread() {
              while (true) {
                     Thread thread = new Thread(new Runnable() {
                            @Override
                            public void run() {
                                   dontStop();
                            }
                     });
                     thread.start();
              }
       }

       public static void main(String[] args) throws Throwable {
              JavaVMStackOOM oom = new JavaVMStackOOM();
              oom.stackLeakByThread();
       }
}

本機方法堆疊

跟虛擬機器堆疊類似,只是一個是虛擬機器執行Java方法,一個是執行Native方法

異常狀況:

StackOverflowError 堆疊深度大於虛擬機器所允許的深度

OOM

Java堆疊

執行緒共享的一塊記憶體區域,從記憶體回收角度來看,基本上都採用分代蒐集演算法,所以分為新生代、老年代。再細緻一點可以分為Eden空間、From Survivor空間、To Survivor空間等。 -Xmx -Xms控制堆空間大小

異常狀況:

1.OOM 堆無法擴展時

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * 
 * java.lang.OutOfMemoryError: Java heap space
 */
public class HeapOOM {

    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();

        while (true) {
            list.add(new OOMObject());
        }
    }
}

方法區

線程間共享。儲存已經被虛擬機器載入的類別資訊、常數、靜態變數、即時編輯器編譯後的程式碼等數據,在HotSpot虛擬機器中可以稱為永生代。

運行時常數在1.6及之前是方法區的一部分(String.intern()動態加入常數池) -XX:MaxPermSize控制大小。在JDK1.7及之後的版本是在Java堆中開闢的一塊記憶體

異常狀況:

OOM

/**
* 需要在JDK1.6上才能复现,JDK1.7及之后版本的JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。
* 在JDK1.7上运行的效果则会一直执行,直到堆内存使用完毕
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M 
*
* java.lang.OutOfMemoryError:PermGen space
*/
public class RuntimeConstantPoolOOM {

public static void main(String[] args) {
    // 使用List保持着常量池引用,避免Full GC回收常量池行为
    List<String> list = new ArrayList<String>();
    // 10MB的PermSize在integer范围内足够产生OOM了
    int i = 0;
    while (true) {
        list.add(String.valueOf(i++).intern());
    }
}
}
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* java.lang.OutOfMemoryError:PermGen space
* 一直创建动态类
*/
public class JavaMethodAreaOOM {

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

static class OOMObject {

}
}

直接記憶體(不屬於虛擬機器運行時的資料區的一部分)

NIO可以使用Native函數函式庫直接分配對外的內存,然後透過儲存在Java對中的DirectByteBuffer物件作為這塊記憶體的參考進行操作。受限於機器物理內存,可以透過-XX:MaxDirectMemorySize制定,如果不制定,預設與Java堆最大值(-Xmx)一樣

異常狀況:

##1.OOM

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 * 
 * java.lang.OutOfMemoryError
 */
public class DirectMemoryOOM {

    private static final int _1MB = 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.allocateMemory(_1MB);
        }
    }
}

以上是詳解JVM 各區域的用途及潛在出現異常的程式碼實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn