對棧,堆,直接內存溢出和異常信息整理詳解,以及JVM參數調優


轉載自:https://blog.csdn.net/BIackMamba/article/details/91046045【對一些錯誤做了修改和添加了調優參數配置】

JVM調優:

-Xms 為jvm啟動時分配的內存,比如-Xms200m,表示分配200M
-Xmx 為jvm運行過程中分配的最大內存,比如-Xms500m,表示jvm進程最多只能夠占用500M內存
-Xss 為jvm啟動的每個線程分配的內存大小,默認JDK1.4中是256K,JDK1.5+中是1M
-server 這個基本不用設置,現代的JVM都是默認是-server模式
啟動命令寫法為:java -jar -Xms256m -Xmx2048m -Xss256k -server xxx.jar
 

OutOfMemoryError異常

Java虛擬機中除了計數器外,虛擬機內存的其他幾個運行時區域都有可能發生OutOfMemoryError異常
Java堆溢出

    概述:
    Java堆用於存儲對象實例,只要不斷地創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那么在對象數量到達最大堆的容量限制后就會產生內存溢出異常。
    Java堆內存中常見的內存溢出異常情況。當出現Java堆內存溢出時,異常堆棧信息"java.lang.OutOfMemoryError”"會跟着進步提示“Java heap space”(Java堆空間)
    要解決這個區域的異常,一般的手段是先通過內存映像分析工具(如Eclipse MemoryAnalyzer)對Dump出來的堆轉儲快照進行分析,重點是確認內存中的對象是否是必要的,也就是要先分清楚到底是出現了內存泄漏(MemoryLeak)還是內存溢出(MemoryOverflow).
    如果是內存泄露,可進一步通過工具在看泄露對象到GC Roots的引用鏈。於是就能找到泄露對象是通過怎樣的路徑與GC Roots相關聯並導致垃圾收集器無法自動回收它們的,掌握了泄露對象的類型信息及GC Roots引用鏈的信息,就可以比較准確地定位出泄露代碼的位置,
    如果不存在泄露,換句話說,就是內存中的對象確實都還必須存活着,那就應當檢查虛擬機的維參數(-Xmx 與 -Xms),與機器物理內存對比看是否還可以調大,從代碼上檢查存在某些對象生命周期過長,持有狀態時間過長的情況,嘗試減少程序運行期的內存消耗
    舉例:Java堆內存溢出異常測試

import java.util.ArrayList;
import java.util.List;

/**
 *  設置限制Java堆的大小為20MB,不可擴展(將堆的最小值-Xms參數與最大直-Xmx參數設置為樣即可避免堆自動擴展)。
 *  通過參數-XX:+HeapDumpOnOutOfMemoryEror可以讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便事后進行分析。
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 * @author xx
 *
 */
public class Demo {
    
    static class OOMObject{
        
    }

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


運行結果:
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:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)


Java虛擬機棧溢出

    由於在HotSpot虛擬機中並不區分虛擬機棧和本地方法棧,因此,對於HotSpot米說,雖然-Xoss參數(設置本地方法棧大小)存在,但實際上是無效的,棧容量只由-Xss參數設定。
    關於虛擬機棧和本地方法棧,在Java虛擬機規范中描述了兩種異常:
    如果線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StackOverflowError 異常。
    如果虛擬機在展棧時無法中請到足夠的內存空間,則拋出OutOfMemoryError 異常.
    舉例:虛擬機棧和本地方法棧OOM測試
    使用-Xss參數減少棧內存容量。結果:拋出StackOverflowError異常,異常出現時輸出的堆線深度相應縮小。
    定義了大量的本地變量,增大此方法賴中本地變量表的長度。結果:拋出StackOverflowError異常時輸出的堆棧深度相應縮小。

/**
 * VM Args: -Xss128k
 * @author 邢鑫
 *
 */
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.printin("stack length:" + oom.stackLength);
             throw e;
         }

     }
}


運行結果:
stack length:2402

Exception in thread "main" java. lang . StackOverflowError

      at org. fenixsoft. oom. VMStackSOF .leak (VMStackSOF . java:20)
      at org. fenixsoft.oom. VMStackSOF . leak (VMStackSOF . java:21)
      at org. fenixsoft. oom. VMStackSOF .leak (VMStacksoF. java :21 )


實驗結果表明:在單個線程下,無論是由於棧械太大還是虛擬機找容量太小,當內存天法分配的時候,虛批機地出的都是StackOverflowError異常。

    操作系統分配給每個進程的內存是有限制的,臂如32位的Windows限制為2GB.虛擬機提供了參數來控制Java堆和方法區的這兩部分內存的最大值。剩余的內存為2GB (操作系統限制)減去Xmx (最大堆容量),再減去MaxPermSize (最大方法區容量),程序計數器消耗內存很小,可以忽略掉。如果虛擬機進程本身耗費的內存不計算在內,剩下的內存就由虛擬機棧和本地方法棧“瓜分”了。每個線程分配到的棧容量越大,可以建立的線程數量自然就越少,建立線程時就越容易把剩下的內存耗盡。
    如果是建立過多線程導致的內存溢出,在不能減少線程數或者更換64位店擬機的情況下,就只能通過減少最大堆和減少棧容量來換取更多的線程。

方法區和運行時常量池溢出

    在運行時常量池是方法區的一部分。
    String intermO是一個Native方法,它的作用是: 如果字符串常量池中已經包含一一個等於此Singl象的字符小期每同代真迪中這個字符串的Sring對象:否則,將此Sring對象仙省的字莉小能加州常晨池中並且返同此Smi象的引用。
    在IDK 16及之前的版本中,由於需量他分配在水久代內,我們可以通過xx PemSi和xx MaPomSie限制方法區大小,從而間接展制其中常量池的容量。
    舉例:運行時常量池導致的內存溢出異常

public class ConstantPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        
        int i =0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}


運行結果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)


從運行結果中可以看到,運行時常量池溢出,在OutOfMemoryError后面跟隨的提示信息是“PermGen space",說明運行時常量池屬於方法區(HotSpot 虛擬機中的永久代)的一部分。而使用JDK 1.7 運行這段程序就不會得到相同的結果,while 循環將一直進行下去。

    方法區用於存放class的相關信息, 如類名,訪問修飾符、常量池、字段描述,方法描述等。
    當前的很多主流框架,如Spring. Hibemate, 在對類進行增強時,都會使用到CGLib這類字節碼技術,增強的類越多,就需要越大的方法區來保證動態生成的Class可以加載人內存。另外,JVM上的動態語言(例如Groovy等)通常都會持續創建類來實現請言的動態性。
    舉例:借助CGLib使方法區出現內存溢出異常

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
 
/**
 * VM Args:- XX:PermSize=10m -XX:MaxPermSize=10m
 */
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() {
                         @Override
                         public Object intercept(Object obj, Method method, Object[] args,
                                    MethodProxy proxy) throws Throwable {
                               return proxy.invoke(obj, args);
                        }
                  });
                  enhancer.create();
            }
      }

      static class OOMObject{
      }
}


運行結果:
outOfMemoryError:permgem space


    方法區溢出產生的情況,使用了CGLih字節碼增強和動態語言,大量ISP或動態產生JSP文件的應用(JSP 第一次運行時需要編譯為Java類)、基於OSGI的應用(即使是同一個類文件,被不同的加載8加載也會視為不同的類)。

本機直接內存溢出

    DircMemory(直接存儲器)容量可通過-XX:MaxDirectMemorySize 指定,如果不指定,則默認與Java堆最大值(-Xmx指定)一樣。
    Unsafe類的getUnsafcO方法限制了只有引導類加載器才會返回實例,也就是設計者希望只有r.jar中的類才能使用Unsafe的功能。
    真正中請分配內存的法是unsafe alocateMemory0。
    舉例:使用unsafe分配本機內存

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


運行結果:
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

 


免責聲明!

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



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