Java虛擬機4:內存溢出


堆溢出

Java堆唯一的作用就是存儲對象實例,只要保證不斷創建對象並且對象不被回收,那么對象數量達到最大堆容量限制后就會產生內存溢出異常了。所以測試的時候把堆的大小固定住並且讓堆不可擴展即可。測試代碼如下

 1 package com.xrq.test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 /**
 7  * 測試內容:堆溢出
 8  *
 9  * 虛擬機參數:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
10  */
11 public class HeapOverflowTest
12 {
13     public static void main(String[] args)
14     {
15         List<HeapOverflowTest> list = new ArrayList<HeapOverflowTest>();
16         while (true)
17         {
18             list.add(new HeapOverflowTest());
19         }
20     }
21 }

運行結果

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8876.hprof ...
Heap dump file created [15782068 bytes in 0.217 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at com.xrq.test.HeapOverflowTest.main(HeapOverflowTest.java:18)

這種異常很常見,也很好發現,因為都提示了“Java heap space”了,定位問題的話,根據異常堆棧分析就好了,行號都有指示。解決方案的話,可以調大堆的大小或者從代碼上檢視是否存在某些對象生命周期過長、持有狀態時間過長的情況,長時間少程序運行期間的內存消耗。

 

棧溢出

Java虛擬機規范中描述了如果線程請求的棧深度太深(換句話說方法調用的深度太深),就會產生棧溢出了。那么,我們只要寫一個無限調用自己的方法,自然就會出現方法調用的深度太深的場景了。測試代碼如下

 1 package com.xrq.test;
 2 
 3 /**
 4  * 測試內容:棧溢出測試(遞歸調用導致棧深度不斷增加)
 5  * 
 6  * 虛擬機參數:-Xss128k
 7  */
 8 public class StackOverflowTest
 9 {
10     private int stackLength = 1;
11     
12     public void stackLeak()
13     {
14         stackLength++;
15         stackLeak();
16     }
17     
18     public static void main(String[] args) throws Throwable
19     {
20         StackOverflowTest stackOverflow = new StackOverflowTest();
21         try
22         {
23             stackOverflow.stackLeak();
24         }
25         catch (Throwable e)
26         {
27             System.out.println("stack length:" + stackOverflow.stackLength);
28             throw e;
29         }        
30     }
31 }

運行結果:

stack length:1006
Exception in thread "main" java.lang.StackOverflowError
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:14)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
  ...

后面都是一樣的,忽略。通過不斷創建線程的方式可以產生OutOfMemoryError,因為每個線程都有自己的棧空間。不過這個操作有危險就不做了,原因是Windows平台下,Java的線程是直接映射到操作系統的內核線程上的,如果寫個死循環無限產生線程,那么可能會造成操作系統的假死。

上面無限產生線程的場景,從另外一個角度說,就是為每個線程的棧分配的內存空間越大,反而越容易產生內存溢出。其實這也很好理解,操作系統分配給進程的內存是有限制的,比如32位的Windows限制為2GB。虛擬機提供了了參數來控制Java堆和方法區這兩部分內存的最大值,剩余內存為2GB-最大堆容量-最大方法區容量,程序計數器很小就忽略了,虛擬機進程本身的耗費也不算,剩下的內存就是棧的了。每個線程分配到的棧容量越大,可建立的線程數自然就越少,建立線程時就越容易把剩下的內存耗盡。

StackOverFlowError這個異常,有錯誤堆棧可以閱讀,比較好定位。而且如果使用虛擬機默認參數,棧深度在大多數情況下,達到1000~2000完全沒有問題,正常方法的調用這個深度應該是完全夠了。但是如果建立過多線程導致的OutOfMemoryError,在不能減少線程數或者更換64位虛擬機的情況下,就只能通過減小最大堆容量和減小棧容量來換取更多的線程了。

 

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

運行時常量池也是方法區的一部分,所以這兩個區域一起看就可以了。這個區域的OutOfMemoryError可以利用String.intern()方法來產生。這是一個Native方法,意思是如果常量池中有一個String對象的字符串就返回池中的這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中去,並且返回此String對象的引用。測試代碼如下

 1 package com.xrq.test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 /**
 7  * 測試內容:常量池溢出(這個例子也可以說明運行時常量池為方法區的一部分)
 8  * 
 9  * 虛擬機參數-XX:PermSize=10M -XX:MaxPermSize=10M
10  */
11 public class ConstantPoolOverflowTest
12 {
13     public static void main(String[] args)
14     {
15         List<String> list = new ArrayList<String>();
16         int i = 0;
17         while (true)
18         {
19             list.add(String.valueOf(i++).intern());
20         }
21     }
22 }

運行結果

Exception in thread "Reference Handler" Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.xrq.test.ConstantPoolOverflowTest.main(ConstantPoolOverflowTest.java:19)
java.lang.OutOfMemoryError: PermGen space
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)

之前有講過,對於HotSpot而言,方法區=永久代,這里看到OutOfMemoryError的區域是“PermGen space”,即永久代,那其實也就是方法區溢出了。注意一下JDK1.7下是不會有這個異常的,while循環將一直下去,因為JDK1.7之后溢出了永久代並采用Native Memory來實現方法區的規划了


免責聲明!

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



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