最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JVM內(nèi)存管理:OutOfMemoryError異常的問題

 更新時(shí)間:2025年03月18日 09:34:09   作者:三余知行  
這篇文章主要介紹了JVM內(nèi)存管理:OutOfMemoryError異常的問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

《Java 虛擬機(jī)規(guī)范》規(guī)定,除了程序計(jì)數(shù)器,其他幾個運(yùn)行時(shí)數(shù)據(jù)區(qū)都有可能 OOM;OOM 與 VM 本身實(shí)現(xiàn)細(xì)節(jié)密切相關(guān),而非 Java 語言約定的公共行為;

1. Java Heap 溢出

OutOfMemoryError:Java heap space,Java Heap 用于存儲對象實(shí)例,只要有足夠的對象沒有被 GC 清除,總會觸及 Heap 上限;(-Xmx 參數(shù)決定);

VM Arguments 設(shè)置

# -Xms 表示堆的最小值
# -Xmx 表示堆的最大值
# -XX:+HeapDumpOnOutOfMemoryError 表示打開開關(guān):VM 出現(xiàn) OOM 時(shí) Dump 當(dāng)前內(nèi)存堆轉(zhuǎn)儲快照
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

示例代碼

循環(huán) new OOMOjbect 對象,直到用完 -Xmx 設(shè)置的內(nèi)存,從而觸發(fā)堆不夠用的異常;

public class HeapOOM {
    static class OOMObject {
    }

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

運(yùn)行結(jié)果

觸發(fā) OutOfMemoryError: Java heap space,并 dump 下快照文件 java_pid34676.hprof;

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid34676.hprof ...
Heap dump file created [27927927 bytes in 0.092 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:267)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
    at java.util.ArrayList.add(ArrayList.java:464)
    at edu.aurelius.jvm.oom.HeapOOM.main(HeapOOM.java:17)

Process finished with exit code 1

Dump 分析

可以通過 JDK 自帶的 Virtual VM 對程序內(nèi)存進(jìn)行查看和分析,從而發(fā)現(xiàn)一些明顯占用內(nèi)存較多的對象,判斷是否就是導(dǎo)致 OOM 的對象;

分析 OOM 問題時(shí)可以從兩方面考慮:內(nèi)存泄漏(Memory Leak,導(dǎo)致 OOM 的對象本該被 GC 回收,因一些程序編寫錯誤導(dǎo)致無法被回收掉)、內(nèi)存溢出(Memory Overflow,導(dǎo)致 OOM 的對象確實(shí)無法被 GC 回收);

Eclipse Memory Analyzer 的 Leak Suspect 功能可以更方便的幫助開發(fā)者找到內(nèi)存泄漏的可疑對象;如下圖所示就是發(fā)現(xiàn)了我們故意循環(huán) new 出的 HeapOOM$OOMObject 對象(點(diǎn)擊 detail 可見 GC Roots 引用鏈);

根據(jù) GC Roots 引用鏈,找到破壞引用路徑的引用從而消除內(nèi)存泄漏問題;

若分析得出導(dǎo)致 OOM 的對象是有必要存在的,則需要從資源層面(如 JVM 堆參數(shù) -Xmx、-Xms,物理可用內(nèi)存)和代碼層面(存儲結(jié)構(gòu)、對象生命周期、引用關(guān)系時(shí)間長短等)進(jìn)行擴(kuò)展或優(yōu)化了;

2. Java VM Stack 與 Native Method Stack 溢出

StackOverflowError,HotSpot VM 并不區(qū)分虛擬機(jī)棧和本地方法棧,對 HotSpot 來說,-Xoss(設(shè)置本地方法棧大小的參數(shù))實(shí)際是無效的,棧容量只能通過 -Xss 參數(shù)來設(shè)置;

《Java 虛擬機(jī)規(guī)范》描述了兩種棧溢出異常:

  • StackOverflowError,線程請求的棧深度大于 VM 所運(yùn)行的最大深度(Xss 允許的內(nèi)存大小用盡);
  • OutOfMemoryError,當(dāng) VM 允許動態(tài)擴(kuò)展棧內(nèi)存,而擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存(進(jìn)程最大內(nèi)存減去 Heap 最大容量、方法區(qū)最大容量、直接內(nèi)存大小、JVM 本身消耗內(nèi)存、程序計(jì)數(shù)器大小,剩下的就是給到所有棧的);

演示 1: 限制棧容量

VM Arguments 設(shè)置

# 設(shè)置 VM Stack 大??;
-Xss160k

不同版本的 JVM 和不同操作系統(tǒng)的棧容量最大大小限制可能不一樣,若設(shè)置的大小小于限制,會得到如下提示信息:

Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

The stack size specified is too small, Specify at least 160k

Process finished with exit code 1

示例代碼

運(yùn)行一段無限遞歸的代碼,用于觸發(fā)棧容量不夠用的異常;

public class JVMStackSOF {
    private int stackLength = 1;

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

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

運(yùn)行結(jié)果

stack length:774
Exception in thread "main" java.lang.StackOverflowError
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:11)
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:12)
  ...
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:12)
    at edu.aurelius.jvm.oom.JVMStackSOF.main(JVMStackSOF.java:18)

Process finished with exit code 1

體現(xiàn)了棧容量太小觸發(fā) StackOverflowError;

演示 2: 定義大量本地變量

示例代碼

在代碼中添加 100 個本地變量,然后進(jìn)行無限遞歸,觸發(fā)棧容量不夠用的異常;

public class JVMStackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41, unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51, unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61, unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71, unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81, unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91, unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        stackLeak();
        unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 = unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 = unused21 = unused22 = unused23 = unused24 = unused25 = unused26 = unused27 = unused28 = unused29 = unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 = unused39 = unused40 = unused41 = unused42 = unused43 = unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 = unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 = unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 = unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 = unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 = unused81 = unused82 = unused83 = unused84 = unused85 = unused86 = unused87 = unused88 = unused89 = unused90 = unused91 = unused92 = unused93 = unused94 = unused95 = unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

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

運(yùn)行結(jié)果

stack length:41
Exception in thread "main" java.lang.StackOverflowError
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:12)
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:13)
  ...
    at edu.aurelius.jvm.oom.JVMStackSOF.stackLeak(JVMStackSOF.java:13)
    at edu.aurelius.jvm.oom.JVMStackSOF.main(JVMStackSOF.java:20)

Process finished with exit code 1

體現(xiàn)了棧幀過大觸發(fā) StackOverflowError;與 示例 1 對比可見,當(dāng)棧容量不變,棧幀變大時(shí),棧的深度(stack length)明顯變小了;

如果是可以動態(tài)擴(kuò)展棧大小的虛擬機(jī)(比如 Classic VM),可能會導(dǎo)致 OutOfMemoryError 而非 StackOverflowError 的結(jié)果;

演示 3: 線程過多

VM Arguments 設(shè)置

# 將 VM Stack 大小設(shè)置得大一些;
-Xss2M

示例代碼

不斷建立不會終止的線程(可能會導(dǎo)致操作系統(tǒng)死機(jī),謹(jǐn)慎操作),把 JVM Stack 和 Native Method Stack 可支配的內(nèi)存耗盡,從而觸發(fā)無法創(chuàng)建本地線程的異常;

public class JVMStackSOF {
    private void dontStop() {
        while (true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            new Thread(() -> dontStop()).start();
        }
    }

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

運(yùn)行結(jié)果

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

如果建立過多內(nèi)存會導(dǎo)致內(nèi)存溢出,而又無法減少線程數(shù)量或增加可用內(nèi)存,可以從限制棧容量、減少堆最大大小的方向考慮優(yōu)化 unable to create native thread;

3. Method Area 與 Runtime Constant Pool 溢出

使用 永久代 或 元空間 來實(shí)現(xiàn)方法區(qū),對方法區(qū)內(nèi)存溢出表現(xiàn)是有影響的;

演示 1: 限制永久代容量

VM Arguments 設(shè)置

# 設(shè)置 Java 堆永久代大小為 6 M;
-XX:PermSize=6M -XX:MaxPermSize=6M

示例代碼

通過持續(xù)往常量池添加 i 的字符串常量,用光 6MB 的永久代,從而觸發(fā)永久代空間不足的異常;

public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        short i = 0;
        while (true) {
            set.add(String.valueOf(i++).intern());
        }
    }
}

運(yùn)行結(jié)果

JDK 6 下的運(yùn)行結(jié)果如下:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at edu.aurelius.jvm.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java: 15)

而在 JDK 7 或更高的版本中(無論是添加 -XX:MaxPermSize 限制,還是添加 JDK 8 才有的 -XX:MaxMetaspaceSize 限制)循環(huán)將會持續(xù)進(jìn)行下去,直至 Heap 溢出(永久代的字符串常量池被移至 Java Heap);可能是常量分配導(dǎo)致 Heap 溢出,也可能是 Set 擴(kuò)容導(dǎo)致 Heap 溢出;

示例 2: String.intern() 影響

String.intern() 調(diào)用的作用是將 首次遇到 的字符串放入字符串常量池;在 JDK 6 中,是將字符串復(fù)制一份到永久代的字符串常量池存儲起來,并返回該字符串實(shí)例的引用;在 JDK 7 后,是將字符串實(shí)例的引用記錄到堆中字符串常量池中;

示例代碼

通過 StringBuilder::new 實(shí)例化兩個字符串,并通過 String.intern() 調(diào)用獲取字符串與原字段進(jìn)行比較;

String str1 = new StringBuilder("計(jì)算機(jī)").append("軟件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);

運(yùn)行結(jié)果

  • JDK 6: false(永久代常量對象引用 vs. 堆對象引用);false(永久代常量對象引用 vs. 堆對象引用);
  • JDK 7: true(堆對象引用 vs. 堆對象引用);false(堆對象引用 vs. 堆對象引用);

示例 3: 模擬方法區(qū)溢出

VM Arguments 設(shè)置

# 設(shè)置 Java 堆永久代大小為 10 M - JDK 7
-XX:PermSize=10M -XX:MaxPermSize=10M
# 設(shè)置元空間大小為 10 M - JDK 8
-XX:MetaspaceSize=10485760 -XX:MaxMetaspaceSize=10485760
# -XX:MinMetaspaceFreeRatio:GC 之后控制最小元空間剩余容量的百分比,減少 GC 頻率;
# -XX:Max-MetaspaceFreeRatio:GC 之后控制最大云空間剩余容量的百分比;

-XX:MetaspaceSize 即元空間初始容量,當(dāng)方法區(qū)使用達(dá)到該值會觸發(fā) GC 進(jìn)行類型卸載,同時(shí)會調(diào)整該值;若釋放大量空間,則降低該值,若釋放少量空間,則在不超過 -XX:MaxMetaspaceSize(默認(rèn) -1,表示不限制)的情況下提高該值;

示例代碼

借助 CGLib 直接操作字節(jié)碼運(yùn)行時(shí)生成大量動態(tài)代碼,從而觸發(fā)方法區(qū)內(nèi)存溢出異常;

public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            enhancer.create();
        }
    }

    static class OOMObject {
    }
}

運(yùn)行結(jié)果

JDK 7 運(yùn)行提示永久代溢出

Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
...

JDK 8 運(yùn)行提示元空間溢出

Caused by: java.lang.OutOfMemoryError: Metaspace
...

4. Direct Memory 溢出

VM Arguments 設(shè)置

# 設(shè)置 Java 堆最大 20 M,直接內(nèi)存最大 10 M;
-Xmx20M -XX:MaxDirectMemorySize=10M

若不指定 -XX:MaxDirectMemorySize,直接內(nèi)存容量將與 Java Heap 容量一致;

示例代碼

代碼通過反射獲取 Unsafe 實(shí)例直接進(jìn)行內(nèi)存分配(越過了 DirectByteBuffer 類,DirectByteBuffer 分配內(nèi)存時(shí),通過計(jì)算得知內(nèi)存不足就會拋出溢出異常,不會真正向操作系統(tǒng)申請內(nèi)存),從而觸發(fā)直接內(nèi)存不夠的異常;

public class DirectMemoryOOM {
    private static final int _5MB = 1024 * 1024 * 5;

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

運(yùn)行結(jié)果

[謹(jǐn)慎操作] -XX:MaxDirectMemorySize=10M 的限制只對 ByteBuffer.allocateDirect(size)new DirectByteBuffer(capacity) 的分配方式有效,對 unsafe.allocateMemory(size) 并無限制,如上代碼會持續(xù)分配內(nèi)存直至讓操作系統(tǒng)死機(jī);

若使用 ByteBuffer.allocateDirect(size) 方法申請直接內(nèi)存,可得到如下結(jié)果:

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:695)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    at edu.aurelius.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

5. GC 頻繁

OutOfMemoryError:GC overhead limit exceeded 當(dāng)超過 98% 的時(shí)間在 GC 時(shí),也會拋出 OOM 異常;

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 基于Java實(shí)現(xiàn)Socket編程入門

    基于Java實(shí)現(xiàn)Socket編程入門

    Java最初是作為網(wǎng)絡(luò)編程語言出現(xiàn)的,使得客戶端和服務(wù)器的溝通變成了現(xiàn)實(shí),而在網(wǎng)絡(luò)編程中,使用最多的就是Socket,本文就來介紹一下基于Java實(shí)現(xiàn)Socket編程入門,感興趣的可以來了解一下
    2022-03-03
  • 關(guān)于JVM默認(rèn)堆內(nèi)存大小問題

    關(guān)于JVM默認(rèn)堆內(nèi)存大小問題

    這篇文章主要介紹了關(guān)于JVM默認(rèn)堆內(nèi)存大小問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • win10系統(tǒng)64位jdk1.8的下載與安裝教程圖解

    win10系統(tǒng)64位jdk1.8的下載與安裝教程圖解

    這篇文章主要介紹了win10系統(tǒng)64位jdk1.8的下載與安裝教程圖解,本文給大家介紹的非常詳細(xì),對大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • SpringBoot實(shí)現(xiàn)PDF添加水印的示例

    SpringBoot實(shí)現(xiàn)PDF添加水印的示例

    本文主要介紹了SpringBoot實(shí)現(xiàn)PDF添加水印的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Java 關(guān)于eclipse導(dǎo)入項(xiàng)目發(fā)生的問題及解決方法(推薦)

    Java 關(guān)于eclipse導(dǎo)入項(xiàng)目發(fā)生的問題及解決方法(推薦)

    下面小編就為大家分享一篇Java 關(guān)于eclipse導(dǎo)入項(xiàng)目發(fā)生的問題及解決方法(推薦),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Spring Security 圖片驗(yàn)證碼功能的實(shí)例代碼

    Spring Security 圖片驗(yàn)證碼功能的實(shí)例代碼

    spring security是一系列的過濾器鏈,所以在這里驗(yàn)證碼也聲明為過濾器,加在過濾器鏈的 登錄過濾器之前,然后自定義一個異常類,來響應(yīng)驗(yàn)證碼的錯誤信息.這篇文章主要介紹了Spring Security 圖片驗(yàn)證碼,需要的朋友可以參考下
    2018-03-03
  • Java實(shí)時(shí)日志輸出至前臺的實(shí)現(xiàn)步驟

    Java實(shí)時(shí)日志輸出至前臺的實(shí)現(xiàn)步驟

    在Java應(yīng)用程序中實(shí)現(xiàn)將實(shí)時(shí)日志輸出至前臺(如Web頁面或桌面應(yīng)用程序),通常涉及到幾個關(guān)鍵組件和技術(shù)的選擇,下面將詳細(xì)介紹如何使用Java實(shí)現(xiàn)實(shí)時(shí)日志輸出至前臺,并結(jié)合具體的實(shí)現(xiàn)技術(shù)和框架來說明,需要的朋友可以參考下
    2025-10-10
  • springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的代碼

    springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的代碼

    這篇文章主要介紹了springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 一篇文章帶你入門java多線程

    一篇文章帶你入門java多線程

    這篇文章主要介紹了java多線程編程實(shí)例,分享了幾則多線程的實(shí)例代碼,具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下
    2021-08-08
  • spring cglib 與 jdk 動態(tài)代理

    spring cglib 與 jdk 動態(tài)代理

    本篇文章主要介紹了spring cglib與jdk動態(tài)代理的相關(guān)知識,具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-05-05

最新評論

多伦县| 邹平县| 宁武县| 黑山县| 屏边| 宿松县| 林西县| 孝感市| 万荣县| 中方县| 马山县| 锡林浩特市| 东阿县| 蒙阴县| 泽普县| 台东市| 高安市| 武乡县| 专栏| 恩施市| 公主岭市| 克什克腾旗| 阳谷县| 法库县| 白银市| 延寿县| 康乐县| 永寿县| 方城县| 娄烦县| 夏邑县| 丘北县| 九寨沟县| 寿阳县| 沈丘县| 临澧县| 顺平县| 长治县| 乌拉特中旗| 连南| 湖口县|