轉載自:https://blog.csdn.net/BIackMamba/article/details/91046045【對一些錯誤做了修改和添加了調優參數配置】
JVM調優:
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)