JVM:Java常見內存溢出異常分析
轉載自://www.importnew.com/14604.html
Java虛(xu)擬機規范規定(ding)JVM的(de)(de)內(nei)存分(fen)為(wei)了好幾塊,比如堆(dui),棧,程(cheng)序(xu)計數器(qi),方法區等,而Hotspot jvm的(de)(de)實(shi)(shi)現中(zhong),將(jiang)堆(dui)內(nei)存分(fen)為(wei)了三部(bu)分(fen),新生代(dai),老(lao)年代(dai),持久帶,其中(zhong)持久帶實(shi)(shi)現了規范中(zhong)規定(ding)的(de)(de)方法區,而內(nei)存模型中(zhong)不(bu)同(tong)的(de)(de)部(bu)分(fen)都會(hui)出現相應的(de)(de)OOM錯誤,接(jie)下來我(wo)們就分(fen)開來討論一下。
棧溢出(chu)(StackOverflowError)
棧溢出拋出java.lang.StackOverflowError錯誤(wu),出現(xian)此種情況是因為方法運行(xing)的時候(hou)棧的深度超過了虛擬機(ji)容許的最大深度所致。
出現(xian)這種情(qing)況,一(yi)般情(qing)況下是(shi)程序錯誤所(suo)致的,比如寫了一(yi)個死遞歸,就(jiu)有(you)可(ke)能造(zao)成此(ci)種情(qing)況。 下面我們通過一(yi)段代(dai)碼(ma)來模(mo)擬一(yi)下此(ci)種情(qing)況的內存溢出。
public class OOMTest{ public void stackOverFlowMethod(){ stackOverFlowMethod(); } public static void main(String... args){ OOMTest oom = new OOMTest(); oom.stackOverFlowMethod(); } }
運行上面的(de)代(dai)碼(ma),會拋(pao)出如下的(de)異常:
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引用起來,然后通過引用鏈來具體分析泄露的原因。
如果(guo)出現了內存溢(yi)出問題,這(zhe)往(wang)往(wang)是程序本生需(xu)要(yao)的內存大于了我們給虛擬機配置的內存,這(zhe)種情(qing)況(kuang)下,我們可(ke)以采用調大-Xmx來(lai)解決這(zhe)種問題。
下(xia)面我們通過(guo)如下(xia)的(de)代碼來演示一下(xia)此種(zhong)情況的(de)溢出(chu):
|
1
2
3
4
5
6
7
8
9
10
|
import java.util.*;import java.lang.*;public class OOMTest{ public static void main(String... args){ List<byte[]> buffer = new ArrayList<byte[]>(); buffer.add(new byte[10*1024*1024]); }} |
我們(men)通過如下的(de)命(ming)令運行(xing)上(shang)面的(de)代(dai)碼:
java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest
程(cheng)序輸入如下(xia)的(de)信息:
|
1
2
3
4
5
|
[GC 1180K->366K(19456K), 0.0037311 secs][Full GC 366K->330K(19456K), 0.0098740 secs][Full GC 330K->292K(19456K), 0.0090244 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at OOMTest.main(OOMTest.java:7) |
從運行結果可(ke)以看出,JVM進行了一(yi)次Minor gc和兩次的Major gc,從Major gc的輸出可(ke)以看出,gc以后old區(qu)使用率為(wei)134K,而字(zi)節(jie)數(shu)組為(wei)10M,加(jia)起來(lai)大于了old generation的空間,所以拋出了異常,如果調(diao)整(zheng)-Xms21M,-Xmx21M,那么就不會(hui)觸發gc操作(zuo)也不會(hui)出現異常了。
通過上面的實驗其實也從側面驗證了一個結論:當對象大于新生代剩余內存的時候,將直接放入老年代,當老年代剩余內存還是無法放下的時候,出發垃圾收集,收集后還是不能放下就會拋出內存溢出異常了
持久帶溢出(OutOfMemoryError: PermGen space)
我們知道Hotspot jvm通過持久帶實現了Java虛擬機規范中的方法區,而運行時的常量池就是保存在方法區中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息占用的內存超過了我們配置。當持久帶溢出的時候拋出java.lang.OutOfMemoryError: PermGen space。
我在工作可能在如下(xia)幾種場景(jing)下(xia)出(chu)現此(ci)問題。
- 使用一些應用服務器的熱部署的時候,我們就會遇到熱部署幾次以后發現內存溢出了,這種情況就是因為每次熱部署的后,原來的class沒有被卸載掉。
- 如果應用程序本身比較大,涉及的類庫比較多,但是我們分配給持久帶的內存(通過-XX:PermSize和-XX:MaxPermSize來設置)比較小的時候也可能出現此種問題。
- 一些第三方框架,比如spring,hibernate都通過字節碼生成技術(比如CGLib)來實現一些增強的功能,這種情況可能需要更大的方法區來存儲動態生成的Class文件。
我們知(zhi)道Java中(zhong)(zhong)字(zi)(zi)符(fu)(fu)串(chuan)常量(liang)是放在常量(liang)池(chi)中(zhong)(zhong)的,String.intern()這個方(fang)法運行的時候(hou),會檢(jian)查常量(liang)池(chi)中(zhong)(zhong)是否(fou)存和本字(zi)(zi)符(fu)(fu)串(chuan)相(xiang)等(deng)的對(dui)象,如(ru)(ru)果(guo)存在直接(jie)返回對(dui)常量(liang)池(chi)中(zhong)(zhong)對(dui)象的引用,不存在的話(hua),先把此字(zi)(zi)符(fu)(fu)串(chuan)加(jia)入常量(liang)池(chi),然后再(zai)返回字(zi)(zi)符(fu)(fu)串(chuan)的引用。那么我們就可以(yi)通過String.intern方(fang)法來模擬一(yi)下(xia)(xia)運行時常量(liang)區的溢出.下(xia)(xia)面我們通過如(ru)(ru)下(xia)(xia)的代碼來模擬此種情(qing)況:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
import java.util.*;import java.lang.*;public class OOMTest{ public static void main(String... args){ List<String> list = new ArrayList<String>(); while(true){ list.add(UUID.randomUUID().toString().intern()); } }} |
我們(men)通(tong)過如下的命令(ling)運行上面代碼:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest
運行后的輸入(ru)如下圖所示(shi):
|
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可以(yi)看出確實是持久帶發生了溢出,這也驗證了,我們前面(mian)說(shuo)的Hotspot jvm通過持久帶來(lai)實現方法區的說(shuo)法。
OutOfMemoryError:unable to create native thread
最后我們在來看看java.lang.OutOfMemoryError:unable to create natvie thread這(zhe)種(zhong)錯誤。 出(chu)現這(zhe)種(zhong)情況的時候(hou),一般是(shi)下(xia)面兩種(zhong)情況導致的:
- 程序創建的線程數超過了操作系統的限制。對于Linux系統,我們可以通過ulimit -u來查看此限制。
- 給虛擬機分配的內存過大,導致創建線程的時候需要的native內存太少。我們都知道操作系統對每個進程的內存是有限制的,我們啟動Jvm,相當于啟動了一個進程,假如我們一個進程占用了4G的內存,那么通過下面的公式計算出來的剩余內存就是建立線程棧的時候可以用的內存。
線程棧總可用內存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計數器占用的內存通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那么留給線程棧可用的空間就越小,在-Xss參數配置的棧容量不變的情況下,可以創建的線程數也就越小。因此如果是因為這種情況導致的unable to create native thread,那么要么我們增大進程所占用的總內存,或者減少-Xmx或者-Xss來達到創建更多線程的目的。
