Java-JVM OutOfMemory 情況(JDK8)


JVM 運行時內存結構(Run-Time Data Areas)

內存溢出分為兩大類:OutOfMemoryError 和 StackOverflowError。

 

一、HeapOomError (JVM 堆內存溢出)

-Xms:初始值
-Xmx:最大值
-Xmn:最小值

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    int i = 0;
    while (true) {
        list.add(new byte[8 * 1024 * 1024]);
        System.out.println(++i);
    }
}

 

二、MemoryLeakOomError(JVM 堆內存泄漏)

Java 語言中是指,未使用的對象仍然在 JVM 堆空間中存在,任保留着引用,無法被 GC。不停的堆積最終觸發堆內存溢出。

 

三、OverheadLimitOomError(垃圾回收超時內存溢出)

# JDK6 新增錯誤類型。當 GC 為釋放很小空間占用大量時間時拋出。
# (超過 98% 以上的時間去釋放小於 2% 的堆空間)
java.lang.OutOfMemoryError: GC overhead limit exceeded

# 關閉上述檢查功能,通常不治本,最終會 Java heap space。應查看系統是否有使用大內存的代碼或死循環
-XX:-UseGCOverheadLimit

static class Key {
    Integer id;
    Key(Integer id) {
        this.id = id;
    }
    // @Override
    // public int hashCode() {
    //     return id.hashCode();
    // }
    //
    // @Override
    // public boolean equals(Object o) {
    //     boolean response = false;
    //     if (o instanceof Key) {
    //         response = (((Key) o).id).equals(this.id);
    //     }
    //     return response;
    // }
}

public static void main(String[] args) {
    Map<Key, String> m = new HashMap<>();
    int x = 0;
    while (true) {
        for (int i = 0; i < 500; i++) {
            if (!m.containsKey(new Key(i))) {
                m.put(new Key(i), "Number:" + i);
            }
        }
        System.out.println(x++);
    }
}

public static void main(String[] args) {
    Map map = System.getProperties();
    Random r = new Random();
    while (true) {
        map.put(r.nextInt(), "Oracle Java");
    }
}

 

四、MetaSpaceOomError(Metaspace內存溢出)

元空間的溢出,系統會拋出 java.lang.OutOfMemoryError: Metaspace。出現該錯誤的原因是類非常多或引用的 jar 包非常多或者通過動態代碼生成類加載等方法,導致元空間的內存占用很大。

JDK8 開始,方法區的實現由永久代(PermGen)變成了元空間(Metaspace)。Metaspace 使用的是本地內存,而不是堆內存,也就是說在默認情況下 Metaspace 的大小只與本地內存大小有關。

# 初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整
# 如果釋放了大量的空間,就適當降低該值。
# 如果釋放了很少的空間,那么在不超過 MaxMetaspaceSize 下適當提高該值。
-XX:MetaspaceSize

# 最大空間,默認沒有限制。
# 防止因為某些情況導致 Metaspace 無限的使用本地內存,影響到其他程序。應設置大小。
-XX:MaxMetaspaceSize


# Metaspace 增長時的最大幅度。
-XX:MaxMetaspaceExpansion

# Metaspace 增長時的最小幅度。
-XX:MinMetaspaceExpansion


# 當進行過Metaspace GC之后, 會計算當前Metaspace的空閑空間比,如果空閑比大於這個參數,那么虛擬機會釋放 Metaspace 的部分空間。在本機該參數的默認值為70,也就是70%。
-XX:MaxMetaspaceFreeRatio

# 當進行過Metaspace GC之后,會計算當前 Metaspace 的空閑空間比,如果空閑比小於這個參數,那么虛擬機將增長 Metaspace 的大小。在本機該參數的默認值為40,也就是40%。
# 設置該參數可以控制 Metaspace 的增長的速度,太小的值會導致 Metaspace 增長的緩慢,Metaspace 的使用逐漸趨於飽和,可能會影響之后類的加載。而太大的值會導致 Metaspace 增長的過快,浪費內存。
-XX:MinMetaspaceFreeRatio

public static void main(String[] args) {
    ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
    // 借助 CGlib 來動態地生成大量的 Class
    while (true) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MetaSpaceOomError.class);
        enhancer.setCallbackTypes(new Class[]{Dispatcher.class, MethodInterceptor.class});
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                return 1;
            }

            @Override
            public boolean equals(Object obj) {
                return super.equals(obj);
            }
        });
        Class clazz = enhancer.createClass();

        System.out.println(clazz.getName() + "===================================");
        // 顯示數量信息(共加載過的類型數目,當前還有效的類型數目,已經被卸載的類型數目)
        System.out.println("total:" + loadingBean.getTotalLoadedClassCount());
        System.out.println("active:" + loadingBean.getLoadedClassCount());
        System.out.println("unloaded:" + loadingBean.getUnloadedClassCount());
    }
}

 

五、DirectoryMemoryOomError(直接內存內存溢出)

在使用 ByteBuffer 中的 allocateDirect() 的時候會出現,很多 Java NIO(如 netty)的框架中被封裝為其他的方法,出現該問題時會拋出 java.lang.OutOfMemoryError: Direct buffer memory。

如果你在直接或間接使用了 ByteBuffer 中的 allocateDirect 方法,而不做 clear 就會出現類似的問題。

# 堆外內存最大值
-XX:MaxDirectMemorySize

private static int ONE_MB = 1024 * 1024;
private static int index = 0;

public static void main(String[] args) {
    try {
        System.out.println(VM.maxDirectMemory() / ONE_MB);
        ByteBuffer.allocateDirect(ONE_MB * 100);

        // 直接內存操作
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        while (true) {
            index++;
            // 分配內存
            long l = unsafe.allocateMemory(ONE_MB);
            // 釋放內存
            unsafe.freeMemory(l);
        }
    } catch (Exception | Error e) {
        System.out.println("index:" + index);
        e.printStackTrace();
    }
}

 

六、StackOomError(棧內存溢出)

當一個線程執行一個Java方法時,JVM將創建一個新的棧幀並且把它push到棧頂。

當一個方法遞歸調用自己時,每層調用都需要創建一個新的棧幀。當棧中越來越多的內存將隨着遞歸調用而被消耗,最終造成 java.lang.StackOverflowError。

# 每個線程的堆棧大小
-Xss

private static int num = 1;

public void testStack() throws StackOverflowError {
    num++;
    this.testStack();
}

public static void main(String[] agrs) {
    try {
        StackOomError t = new StackOomError();
        t.testStack();
    } catch (StackOverflowError stackOverflowError) {
        System.out.println(num);
        stackOverflowError.printStackTrace();
    }
}

 

七、ArrayLimitOomError(數組超限內存溢出)

JVM 對應用程序所能分配數組最大大小是有限制的,Java 數組的索引是 int 類型,不同的平台限制有所不同。

在為數組分配內存之前,會執行特定平台的檢查:分配的數據結構是否在此平台是可尋址的。

若數組長度超出系統上限就會造成 java.lang.OutOfMemoryError: Requested array size exceeds VM limit。

public static void main(String[] args) {
    try {
        int[] arr = new int[Integer.MAX_VALUE];
    } catch (Throwable t) {
        t.printStackTrace();
    }
}

 


https://segmentfault.com/a/1190000017226359

https://blog.csdn.net/bolg_hero/article/details/78189621


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM