JVM學習筆記(3)---OutOfMemory詳解

nintyuui發表於2021-09-09

堆溢位

之前說過,堆中主要儲存的是物件例項。
所以如果不斷建立物件,並保證GC Roots(之後會說明)到物件間有可達路徑來避免垃圾回收機制清除這些物件,就會在物件數量達到堆的容量限制後產生記憶體溢位。

異常示例程式碼如下:
/**
 * 堆溢位
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * -Xms設定堆的最小值 -Xmx設定堆的最大值
 *
 */public class HeapOOM {    static class OOMObject {

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

}

透過-Xms和-Xmx設定Vm最小、最大堆的值為20m(將最大最小值設為一樣可以避免堆的自動擴充套件)

棧溢位

HotSpot虛擬機器不區分虛擬機器棧和本地方法棧,所以這裡統稱為棧溢位
之前已經討論過,虛擬機器棧會丟擲兩種異常,StackOverflowErrorOutOfMemoryError 異常。(見

Java虛擬機器規範中,對該區域規定了兩種異常:

  • StackOverFlowError:執行緒請求的棧深度大於虛擬機器允許的棧深度

  • OverOfMemoryError:動態擴充套件的執行緒無法申請到足夠的記憶體

簡單理解來看:

  1. 對於前者而言,是由於增加過大棧幀深度或限制虛擬機器棧記憶體產生。
    單執行緒中,我們不斷增大棧幀中本地變數表的長度(如定義大量的本地變數),或者限制棧記憶體容量(透過Vm啟動引數),都輸出StackOverFlowError

  2. 對於後者而言,是由於不斷建立執行緒產生。
    這樣產生的 OutOfMemoryError 與棧空間大小不存在關係。在記憶體總量一定時,每個執行緒的棧分配的記憶體越大,則越容易產生記憶體溢位。

異常示例程式碼如下:
/**
 * 棧溢位 OutOfMemoryError
 * VM Args:-Xss2m
 * -Xss設定棧大小 來減小棧容量
 * 執行本程式碼前請先儲存當前電腦環境,可能假死
 */public class StackOOM {    public int threadCount = 0;    public void addThread() {        while (true) {
            Thread thread = new Thread(new Runnable() {                public void run() {                    while (true) {
                    }
                }
            });
            thread.start();
        }
    }    public static void main(String[] args){
        StackOOM stackOOM = new StackOOM();
        stackOOM.addThread();
    }
}

直接記憶體溢位

在JDK1.4之後,新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以直接使用Native函式庫分配堆外記憶體,透過一個儲存在Java堆中的 DirectByteBuffer物件 作為這塊記憶體的引用進行操作。
這樣能在一些場景中顯著提高效能,避免了在Java堆和Native堆中來回複製資料。

透過建立Unsafe例項,可以申請分配直接記憶體。透過MaxDirectMemorySize引數,可以指定直接記憶體容量,當申請的直接記憶體過大時,會出現直接記憶體溢位。

異常示例程式碼如下:
/**
 * Vm Args: -Xmx20M -xx:MaxDirectMemorySize=10M
 *
 */public class DirectMemoryOOM {    private static final int _1MB = 1024*1024;    public static void main(String[] args)throws Exception{        //透過反射獲取Unsafe例項,來申請記憶體分配
        Field unsafeFiled = Unsafe.class.getDeclaredFields()[0];
        unsafeFiled.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeFiled.get(null);        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

元空間溢位

我們已知,方法區中主要存放的是一些描述性資訊,即後設資料元空間是方法區的一種實現方式。
(注意,方法區是一種規範元空間 和 永久代 都是一種實現

在JDK1.8之前,使用 永久代(PermGen)來實現方法區,但有以下問題:

  • 永久代記憶體經常不夠用或發生記憶體洩露,爆出異常 OutOfMemoryError: PermGen

  • 動態類載入的情況越來越多,這塊記憶體我們變得不太可控。

  • 類及方法的資訊等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢位,太大則容易導致老年代溢位。

  • 永久代會為 GC 帶來不必要的複雜度,並且回收效率偏低。

在jdk8之後,用元空間(MetaSpace)替代。 元空間是和本地記憶體相關的。預設上限大小是本地記憶體。

元空間與永久代之間 最大區別 在於:
元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制。

異常程式碼如下:
import net.sf.cglib.beans.BeanGenerator;import net.sf.cglib.beans.BeanMap;import java.util.*;/**
 * VM args: -XX:MaxMetaspaceSize=1M -XX:+PrintGCDetails
 */public class MetaSpaceOOM {    public static void main(String[] args) throws Exception {        for (int i = 0; i < 80000; i++) {            //動態建立類
            Map<Object, Object> propertyMap = new HashMap<Object, Object>();
            propertyMap.put("id", Class.forName("java.lang.Integer"));            //建立一個動態生成bean
            CglibBean bean = new CglibBean(propertyMap);            //給 Bean 設定值
            bean.setValue("id", new Random().nextInt(100));            //列印 Bean的屬性id
            System.out.println("num=" + i + "  id=" + bean.getValue("id"));
        }
    }    static class CglibBean {        /**
         * 實體Object
         */
        public Object object = null;        /**
         * 屬性map
         */
        public BeanMap beanMap = null;        public CglibBean() {            super();
        }        @SuppressWarnings("unchecked")        public CglibBean(Map propertyMap) {            this.object = generateBean(propertyMap);            this.beanMap = BeanMap.create(this.object);
        }        /**
         * 給bean屬性賦值
         * @param property 屬性名
         * @param value 值
         */
        public void setValue(String property, Object value) {
            beanMap.put(property, value);
        }        /**
         * 透過屬性名得到屬性值
         * @param property 屬性名
         * @return 值
         */
        public Object getValue(String property) {            return beanMap.get(property);
        }        /**
         * 得到該實體bean物件
         * @return
         */
        public Object getObject() {            return this.object;
        }        @SuppressWarnings("unchecked")        private Object generateBean(Map propertyMap) {
            BeanGenerator generator = new BeanGenerator();
            Set keySet = propertyMap.keySet();            for (Iterator i = keySet.iterator(); i.hasNext();) {
                String key = (String) i.next();
                generator.addProperty(key, (Class) propertyMap.get(key));
            }            return generator.create();
        }
    }

}



作者:weberweber
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3209/viewspace-2818118/,如需轉載,請註明出處,否則將追究法律責任。

相關文章