Java常见内存溢出异常分析
Java虛擬機規范規定JVM的內存分為了好幾塊,比如堆,棧,程序計數器,方法區等,而Hotspot jvm的實現中,將堆內存分為了三部分,新生代,老年代,持久帶,其中持久帶實現了規范中規定的方法區,而內存模型中不同的部分都會出現相應的OOM錯誤,接下來我們就分開來討論一下。
棧溢出(StackOverflowError)
棧溢出拋出java.lang.StackOverflowError錯誤,出現此種情況是因為方法運行的時候棧的深度超過了虛擬機容許的最大深度所致。
出現這種情況,一般情況下是程序錯誤所致的,比如寫了一個死遞歸,就有可能造成此種情況。 下面我們通過一段代碼來模擬一下此種情況的內存溢出。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | importjava.util.*; importjava.lang.*; publicclass OOMTest{ ??publicvoid stackOverFlowMethod(){ ??????stackOverFlowMethod(); ??} ??publicstatic void main(String... args){ ??????OOMTest oom = newOOMTest(); ??????oom.stackOverFlowMethod(); ??} } |
運行上面的代碼,會拋出如下的異常:
| 1 2 | Exception in thread "main"java.lang.StackOverflowError ????????at OOMTest.stackOverFlowMethod(OOMTest.java:6) |
堆溢出(OutOfMemoryError:java heap space)
堆內存溢出的時候,虛擬機會拋出java.lang.OutOfMemoryError:java heap space,出現此種情況的時候,我們需要根據內存溢出的時候產生的dump文件來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動參數)。出現此種問題的時候有可能是內存泄露,也有可能是內存溢出了。
如果內存泄露,我們要找出泄露的對象是怎么被GC ROOT引用起來,然后通過引用鏈來具體分析泄露的原因。
如果出現了內存溢出問題,這往往是程序本生需要的內存大于了我們給虛擬機配置的內存,這種情況下,我們可以采用調大-Xmx來解決這種問題。
下面我們通過如下的代碼來演示一下此種情況的溢出:
| 1 2 3 4 5 6 7 8 9 10 | importjava.util.*; importjava.lang.*; publicclass OOMTest{ ????????publicstatic void main(String... args){ ????????????????List<byte[]> buffer = newArrayList<byte[]>(); ????????????????buffer.add(newbyte[10*1024*1024]); ????????} } |
我們通過如下的命令運行上面的代碼:
java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest
程序輸入如下的信息:
| 1 2 3 4 5 | [GC 1180K->366K(19456K), 0.0037311secs] [Full GC 366K->330K(19456K), 0.0098740secs] [Full GC 330K->292K(19456K), 0.0090244secs] Exception in thread "main"java.lang.OutOfMemoryError: Java heap space ????????at OOMTest.main(OOMTest.java:7) |
從運行結果可以看出,JVM進行了一次Minor gc和兩次的Major gc,從Major gc的輸出可以看出,gc以后old區使用率為134K,而字節數組為10M,加起來大于了old generation的空間,所以拋出了異常,如果調整-Xms21M,-Xmx21M,那么就不會觸發gc操作也不會出現異常了。
通過上面的實驗其實也從側面驗證了一個結論:當對象大于新生代剩余內存的時候,將直接放入老年代,當老年代剩余內存還是無法放下的時候,出發垃圾收集,收集后還是不能放下就會拋出內存溢出異常了
持久帶溢出(OutOfMemoryError: PermGen space)
我們知道Hotspot jvm通過持久帶實現了Java虛擬機規范中的方法區,而運行時的常量池就是保存在方法區中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息占用的內存超過了我們配置。當持久帶溢出的時候拋出java.lang.OutOfMemoryError: PermGen space。
我在工作可能在如下幾種場景下出現此問題。
我們知道Java中字符串常量是放在常量池中的,String.intern()這個方法運行的時候,會檢查常量池中是否存和本字符串相等的對象,如果存在直接返回對常量池中對象的引用,不存在的話,先把此字符串加入常量池,然后再返回字符串的引用。那么我們就可以通過String.intern方法來模擬一下運行時常量區的溢出.下面我們通過如下的代碼來模擬此種情況:
| 1 2 3 4 5 6 7 8 9 10 11 12 | importjava.util.*; importjava.lang.*; publicclass OOMTest{ ????????publicstatic void main(String... args){ ????????????????List<String> list = newArrayList<String>(); ????????????????while(true){ ????????????????????????list.add(UUID.randomUUID().toString().intern()); ????????????????} ????????} } |
我們通過如下的命令運行上面代碼:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest
運行后的輸入如下圖所示:
| 1 2 3 | Exception in thread "main"java.lang.OutOfMemoryError: PermGen space ????????at java.lang.String.intern(Native Method) ????????at OOMTest.main(OOMTest.java:8) |
通過上面的代碼,我們成功模擬了運行時常量池溢出的情況,從輸出中的PermGen space可以看出確實是持久帶發生了溢出,這也驗證了,我們前面說的Hotspot jvm通過持久帶來實現方法區的說法。
OutOfMemoryError:unable to create native thread
最后我們在來看看java.lang.OutOfMemoryError:unable to create natvie thread這種錯誤。 出現這種情況的時候,一般是下面兩種情況導致的:
總結
以上是生活随笔為你收集整理的Java常见内存溢出异常分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Web系列:Spring MV
- 下一篇: Java并发:volatile内存可见性