溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java內(nèi)存溢出的處理方法是什么

發(fā)布時(shí)間:2022-11-11 09:23:42 來源:億速云 閱讀:108 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Java內(nèi)存溢出的處理方法是什么”,在日常操作中,相信很多人在Java內(nèi)存溢出的處理方法是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java內(nèi)存溢出的處理方法是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

    1、Java堆溢出

    Java堆用于儲(chǔ)存對象實(shí)例,我們只要不斷地創(chuàng)建對象,并且保證GC Roots到對象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對象,那么隨著對象數(shù)量的增加,總?cè)萘坑|及最大堆的容量限制后就會(huì)產(chǎn)生內(nèi)存溢出異常。

    1、案例創(chuàng)建

    需要手動(dòng)調(diào)節(jié)JVM參數(shù),不然需要等很長時(shí)間:-Xms20m -Xmx20m

     public class JavaHeapDemo {
     
         static class OOMObject {
     
         }
         public static void main(String[] args) {
             List<OOMObject> list = new ArrayList<OOMObject>();
             //利用while循環(huán)不斷創(chuàng)建對象
             while (true) {
                 list.add(new OOMObject());
             }
         }
     }

    Java內(nèi)存溢出的處理方法是什么

    2、處理方法

    常規(guī)的處理方法是首先通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對Dump出來的堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析

    1. 分清楚到底是出現(xiàn)了內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)

    2. 內(nèi)存泄漏:通過工具查看泄漏對象到GC Roots的引用鏈,找到泄漏對象是通過怎樣的引用路徑、與哪些GC Roots相關(guān)聯(lián),才導(dǎo)致垃圾收集器無法回收它們,根據(jù)泄漏對象的類型信息以及它到GC Roots引用鏈的信息,一般可以比較準(zhǔn)確地定位到這些對象創(chuàng)建的位置,進(jìn)而找出產(chǎn)生內(nèi)存泄漏的代碼的具體位置

    3. 內(nèi)存溢出:檢查Java虛擬機(jī)的堆參數(shù)(-Xmx與-Xms)設(shè)置,與機(jī)器的內(nèi)存對比,看看是否還有向上調(diào)整的空間。再從代碼上檢查 是否存在某些對象生命周期過長、持有狀態(tài)時(shí)間過長、存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)不合理等情況,盡量減少程序運(yùn)行期的內(nèi)存消耗

    2、虛擬機(jī)棧和本地方法棧溢出

    關(guān)于虛擬機(jī)棧和本地方法棧,在《Java虛擬機(jī)規(guī)范》中描述了兩種異常:

    • 如果線程請求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常。

    • 如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展棧容量無法申請到足夠的內(nèi)存時(shí),將拋出OutOfMemoryError異常

    《Java虛擬機(jī)規(guī)范》明確允許Java虛擬機(jī)實(shí)現(xiàn)自行選擇是否支持棧的動(dòng)態(tài)擴(kuò)展,而HotSpot虛擬機(jī)的選擇是不支持?jǐn)U展,所以除非在創(chuàng)建

    線程申請內(nèi)存時(shí)就因無法獲得足夠內(nèi)存而出現(xiàn)OutOfMemoryError異常,否則在線程運(yùn)行時(shí)是不會(huì)因?yàn)閿U(kuò)展而導(dǎo)致內(nèi)存溢出的,只會(huì)因?yàn)?/p>

    棧容量無法容納新的棧幀而導(dǎo)致StackOverflowError異常

    1、使用-Xss參數(shù)減少棧內(nèi)存容量

     public class JavaVMStackSOF {
     
         private int stackLength = 1;
     
         public void stackLength() {
             stackLength++;
             //無限遞歸
             stackLength();
         }
     
         public static void main(String[] args) {
             JavaVMStackSOF sof = new JavaVMStackSOF();
             try {
                 sof.stackLength();
             } catch (Throwable e) {
                 System.out.println("stack length:" + sof.stackLength);
                 throw e;
             }
         }
     }

    這里可以通過指定參數(shù)-Xss128k,用來測試棧溢出的情況

    3、方法區(qū)和運(yùn)行時(shí)常量池溢出

    HotSpot從JDK 7開始逐步“去永久代”的計(jì)劃,并在JDK 8中完全使用元空間來代替永久代的背景故事,使用“永久代”還是“元空間”來

    實(shí)現(xiàn)方法區(qū),對程序有什么實(shí)際的影響。

    String::intern()是一個(gè)本地方法,它的作用是如果字符串常量池中已經(jīng)包含一個(gè)等于此String對象的字符串,則返回代表池中這個(gè)字符串的

    String對象的引用;否則,會(huì)將此String對象包含的字符串添加到常量池中,并且返回此String對象的引用。

    這里測試需要JDK6:-XX:PermSize=6M -XX:MaxPermSize=6M

     public class RuntimeConstantPoolOOM {
     
         public static void main(String[] args) {
             // 使用Set保持著常量池引用,避免Full GC回收常量池行為
             Set<String> set = new HashSet<String>();
             // 在short范圍內(nèi)足以讓6MB的PermSize產(chǎn)生OOM了
             short i = 0;
             while (true) {
                 set.add(String.valueOf(i++).intern());
             }
         }
     }

    JDK8模擬測試

     package jdk8;
     
     import java.io.File;
     import java.lang.management.ClassLoadingMXBean;
     import java.lang.management.ManagementFactory;
     import java.net.URL;
     import java.net.URLClassLoader;
     import java.util.ArrayList;
     import java.util.List;
     
     /**
      *
      * @ClassName:OOMTest
      * @Description:模擬類加載溢出(元空間oom)
      * 為了快速溢出,設(shè)置參數(shù):-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m
      * @author diandian.zhang
      */
     public class OOMTest {
         public static void main(String[] args) {
             try {
                 //準(zhǔn)備url
                 URL url = new File("D:/58workplace/11study/src/main/java/jdk8").toURI().toURL();
                 URL[] urls = {url};
                 //獲取有關(guān)類型加載的JMX接口
                 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
                 //用于緩存類加載器
                 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
                 while (true) {
                     //加載類型并緩存類加載器實(shí)例
                     ClassLoader classLoader = new URLClassLoader(urls);
                     classLoaders.add(classLoader);
                     classLoader.loadClass("ClassA");
                     //顯示數(shù)量信息(共加載過的類型數(shù)目,當(dāng)前還有效的類型數(shù)目,已經(jīng)被卸載的類型數(shù)目)
                     System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
                     System.out.println("active: " + loadingBean.getLoadedClassCount());
                     System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }

    方法區(qū)溢出也是一種常見的內(nèi)存溢出異常,一個(gè)類如果要被垃圾收集器回收,要達(dá)成的條件是比較苛刻的。在經(jīng)常運(yùn)行時(shí)生成大量動(dòng)態(tài)類的應(yīng)用場景里,就應(yīng)該特別關(guān)注這些類的回收狀況。這類場景除了之前提到的程序使用了CGLib字節(jié)碼增強(qiáng)和動(dòng)態(tài)語言外,常見的還有:大量JSP或動(dòng)態(tài)產(chǎn)生JSP文件的應(yīng)用(JSP第一次運(yùn)行時(shí)需要編譯為Java類)、基于OSGi的應(yīng)用(即使是同一個(gè)類文件,被不同的加載器加載也會(huì)視為不同的類)等。

    在JDK 8以后,永久代便完全退出了歷史舞臺(tái),元空間作為其替代者登場。在默認(rèn)設(shè)置下,前面列舉的那些正常的動(dòng)態(tài)創(chuàng)建新類型的測試用例已經(jīng)很難再迫使虛擬機(jī)產(chǎn)生方法區(qū)的溢出異常了。不過為了讓使用者有預(yù)防實(shí)際應(yīng)用里出現(xiàn)類似于代碼清單2-9那樣的破壞性的操作,HotSpot還是提供了一些參數(shù)作為元空間的防御措施,主要包括:

    • -XX:MaxMetaspaceSize:設(shè)置元空間最大值,默認(rèn)是-1,即不限制,或者說只受限于本地內(nèi)存大小。

    • -XX:MetaspaceSize:指定元空間的初始空間大小,以字節(jié)為單位,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)收集器會(huì)對該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過-XX:MaxMetaspaceSize(如果設(shè)置了的話)的情況下,適當(dāng)提高該值。

    • -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空間剩余容量的百分比,可減少因?yàn)樵臻g不足導(dǎo)致的垃圾收集的頻率。類似的還有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空間剩余容量的百分比。

    4、本機(jī)直接內(nèi)存溢出

    直接內(nèi)存(Direct Memory)的容量大小可通過-XX:MaxDirectMemorySize參數(shù)來指定,如果不去指定,則默認(rèn)與Java堆最大值(由-Xmx指定)一致。

    JVM參數(shù):-Xmx20M -XX:MaxDirectMemorySize=10M

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

    Java內(nèi)存溢出的處理方法是什么

    越過了DirectByteBuffer類直接通過反射獲取Unsafe實(shí)例進(jìn)行內(nèi)存分配(Unsafe類的getUnsafe()方法指定只有引導(dǎo)類加載器才會(huì)返回實(shí)例,體現(xiàn)了設(shè)計(jì)者希望只有虛擬機(jī)標(biāo)準(zhǔn)類庫里面的類才能使用Unsafe的功能,在JDK 10時(shí)才將Unsafe的部分功能通過VarHandle開放給外部使用),因?yàn)殡m然使用DirectByteBuffer分配內(nèi)存也會(huì)拋出內(nèi)存溢出異常,但它拋出異常時(shí)并沒有真正向操作系統(tǒng)申請分配內(nèi)存,而是通過計(jì)算得知內(nèi)存無法分配就會(huì)在代碼里手動(dòng)拋出溢出異常,真正申請分配內(nèi)存的方法是Unsafe::allocateMemory()。

    到此,關(guān)于“Java內(nèi)存溢出的處理方法是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI