Java與C++之間有一堵由內存動態分配和垃圾收集技術所圍成的高墻,墻外面的人想進去,墻里面的人卻想出來。
按照《Java虛擬機規范(第2版)》的規定,Java虛擬機所管理的內存將包括以下幾個運行時數據區域,來個圖更加直觀點,如下圖所示:
解釋下各個部分
?
程序計數器:
Program Counter Register是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。 每個線程都有一個獨立的程序計數器,各個線程之間計數器互不影響,獨立存儲。此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
?
Java虛擬機棧:
也是線程私有的,它的生命周期與線程相同。每個方法被執行的時候會同時創建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機中從入棧到出棧的過程。
如果線程請求棧深度大于虛擬機所允許的深度,拋出StackOverflowError
如果虛擬機棧可以動態擴展,擴展時無法申請到足夠的內存時會拋出OutOfMemoryError
?
本地方法棧:
Native Method Stacks與虛擬機棧所發揮的作用是非常相似的,只不過一個是執行Java方法,一個是Nataive方法,HotSpot虛擬機直接將兩者合二為一了
?
Java堆:
Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。Java堆是垃圾收集器管理的主要區域,很多時候稱為GC堆。
如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError
?
方法區:
Method Area與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、JIT編譯后的代碼等數據。
當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError。
?
運行時常量池:
Runtime Constant Pool是方法區的一部分。用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中。
當常量池無法再申請到內存時會拋出OutOfMemoryError異常。
?
直接內存:
Direct Memory并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,但是這部分也是頻繁使用。在Java的NIO中使用到,服務器管理員忽略直接內存后果是,各個內存區域總和大于物理內存限制,從而導致動態擴展時出現OutOfMemoryError異常。
?
?
?實戰:OutOfMemoryError異常:
1,Java堆溢出:
Java堆用于存儲對象實例,我們只要不斷創建對象,并且保證GC Roots到對象之間有可達路徑來避免GC清除這些對象,就會在對象數量到達最大堆的容量限制后產生內存溢出異常。
VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
XX:+HeapDumpOnOutOfMemoryError這個參數可以讓虛擬機在出現內存溢出異常時Dump出當前的內存堆轉儲快照以便事后進行分析。
?
import java.util.ArrayList; import java.util.List; /** * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError * @author Administrator * */ public class HeapOOM { static class OOMObject{ private String name; public OOMObject(String name) { this.name = name; } } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); long i = 1; while(true) { list.add(new OOMObject("IpConfig..." + i++)); } } }?拋出的異常:
?
Dumping heap to java_pid27828.hprof ...
Heap dump file created [14123367 bytes in 0.187 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:45)
at java.lang.StringBuilder.<init>(StringBuilder.java:92)
at com.baoxian.HeapOOM.main(HeapOOM.java:22)
?
?注:出現Java堆內存溢出時,異常堆棧信息?java.lang.OutOfMemoryError?后面會緊跟著?Java heap space。
要解決這個異常,一般手段是首先通過內存映像分析工具比如Eclipse Memory Analyzer對dump出來的堆轉儲快照進行分析,重點是確認內存中對象是否是必要的,也就是要弄清楚到底是出現了內存泄露 Memory Leak還是內存溢出 Memory Overflow。
如果是內存泄露,可進一步通過工具查看泄露對象到GC Roots的引用鏈。于是就能找到泄露對象時通過怎樣的路徑與GC Roots相關聯并導致垃圾收集器無法自動回收它們。掌握了泄露對象的類型信息,以及GC Roots引用鏈的信息,就可以比較準確的定位出泄露代碼的位置了。
如果不存在泄露,那么就該修改-Xms 和 -Xms堆參數看能否加大點。
?
2,虛擬機棧和本地方法棧溢出
-Xoss參數設置本地方法棧大小,對于HotSpot沒用。棧容量只由-Xss參數設定。
?
/** * VM Args: -Xss128k * @author Administrator * */ 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.println("stack length: " + oom.stackLength); throw e; } } }?
?
?拋出異常:
stack length: 1007
Exception in thread "main" java.lang.StackOverflowError
at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at com.baoxian.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)。。。。
?
3,運行時常量池溢出:
運行時常量池分配在方法區內,可以通過 -XX:PermSize和 -XX:MaxPermSize限制方法區大小,從而間接限制其中常量池的容量。
?
import java.util.ArrayList; import java.util.List; /** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M * @author Administrator * */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持著常量池引用,避免Full GC回收常量池行為 List<String> list = new ArrayList<String>(); // 10MB的PermSize在integer范圍內足夠產生OOM了 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 com.baoxian.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)
運行時常量池溢出,在java.lang.OutOfMemoryError后面緊跟著是PermGen space
?
4,方法區溢出:
方法區用于存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述符、方法描述等。對于這個區域的測試,基本的思路是運行時產生大量的類去填滿方法區,直到溢出。比如動態代理會生成動態類。
使用CGLib技術直接操作字節碼運行,生成大量的動態類。當前很多主流框架如Spring和Hibernate對類進行增強都會使用CGLib這類字節碼技術,增強的類越多,就需要越大的方法區來保證動態生成的Class可以加載入內存。
異常:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
同樣,跟常量池一樣,都是PermGen space字符串出現
方法區溢出也是一種常見的內存溢出異常,一個類如果要被垃圾收集器回收,判定條件是非常苛刻的。在經常動態生成大量Class的應用中,需要特別注意類的回收狀況。這類場景除了上面提到的程序使用GCLib字節碼技術外,常見的還有: 大量JSP或動態產生的JSP文件的應用(JSP第一次運行時需要編譯為Java類)、基于OSGi應用等。
?
5,本機直接內存溢出:
DirectMemory容量可以通過-XX:MaxDirectMemorySize指定,如果不指定,則默認與Java堆的最大值-Xmx指定一樣。
/** * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M * @author Administrator * */ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while(true) { unsafe.allocateMemory(_1MB); } } }
?
在OutOfMemoryError后面不會有任何東西了,這就是DirectMemory內存溢出了。
?
本人博客已搬家,新地址為: http://yidao620c.github.io/
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
