JVM:Java常見內存溢出異常分析

轉載自:http://www.importnew.com/14604.htmlhtml

Java虛擬機規範規定JVM的內存分爲了好幾塊,好比堆,棧,程序計數器,方法區等,而Hotspot jvm的實現中,將堆內存分爲了三部分,新生代,老年代,持久帶,其中持久帶實現了規範中規定的方法區,而內存模型中不一樣的部分都會出現相應的OOM錯誤,接下來咱們就分開來討論一下。java

棧溢出(StackOverflowError)spring

棧溢出拋出java.lang.StackOverflowError錯誤,出現此種狀況是由於方法運行的時候棧的深度超過了虛擬機允許的最大深度所致。數組

出現這種狀況,通常狀況下是程序錯誤所致的,好比寫了一個死遞歸,就有可能形成此種狀況。 下面咱們經過一段代碼來模擬一下此種狀況的內存溢出。服務器

public class OOMTest{
 
  public void stackOverFlowMethod(){
      stackOverFlowMethod();
  }
 
  public static void main(String... args){
      OOMTest oom = new OOMTest();
      oom.stackOverFlowMethod();
  }
}

 

運行上面的代碼,會拋出以下的異常:多線程

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來解決這種問題。框架

下面咱們經過以下的代碼來演示一下此種狀況的溢出:dom

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 ]);
         }
 
}

咱們經過以下的命令運行上面的代碼:jvm

java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTestspa

程序輸入以下的信息:

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 )

從運行結果能夠看出,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
我在工做可能在以下幾種場景下出現此問題。

  1. 使用一些應用服務器的熱部署的時候,咱們就會遇到熱部署幾回之後發現內存溢出了,這種狀況就是由於每次熱部署的後,原來的class沒有被卸載掉。
  2. 若是應用程序自己比較大,涉及的類庫比較多,可是咱們分配給持久帶的內存(經過-XX:PermSize和-XX:MaxPermSize來設置)比較小的時候也可能出現此種問題。
  3. 一些第三方框架,好比spring,hibernate都經過字節碼生成技術(好比CGLib)來實現一些加強的功能,這種狀況可能須要更大的方法區來存儲動態生成的Class文件。

咱們知道Java中字符串常量是放在常量池中的,String.intern()這個方法運行的時候,會檢查常量池中是否存和本字符串相等的對象,若是存在直接返回對常量池中對象的引用,不存在的話,先把此字符串加入常量池,而後再返回字符串的引用。那麼咱們就能夠經過String.intern方法來模擬一下運行時常量區的溢出.下面咱們經過以下的代碼來模擬此種狀況:

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());
                 }
         }
 
}

咱們經過以下的命令運行上面代碼:

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這種錯誤。 出現這種狀況的時候,通常是下面兩種狀況致使的:

    1. 程序建立的線程數超過了操做系統的限制。對於Linux系統,咱們能夠經過ulimit -u來查看此限制。
    2. 給虛擬機分配的內存過大,致使建立線程的時候須要的native內存太少。咱們都知道操做系統對每一個進程的內存是有限制的,咱們啓動Jvm,至關於啓動了一個進程,假如咱們一個進程佔用了4G的內存,那麼經過下面的公式計算出來的剩餘內存就是創建線程棧的時候能夠用的內存。 線程棧總可用內存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計數器佔用的內存 經過上面的公式咱們能夠看出,-Xmx 和 MaxPermSize的值越大,那麼留給線程棧可用的空間就越小,在-Xss參數配置的棧容量不變的狀況下,能夠建立的線程數也就越小。所以若是是由於這種狀況致使的unable to create native thread,那麼要麼咱們增大進程所佔用的總內存,或者減小-Xmx或者-Xss來達到建立更多線程的目的。
相關文章
相關標籤/搜索