天天看點

記憶體溢出及解決方案什麼是記憶體溢出

什麼是記憶體溢出

JVM運作過程中,程式不斷的申請記憶體空間用于儲存運作時資料,當程式申請的記憶體空間系統無法滿足時,就會抛出記憶體溢出錯誤。記憶體溢出發生的區域以及相應的解決方案都不相同,下面我們逐一分析記憶體溢出類型及解決方案。

OutOfMemoryError與StackOverflowError

JVM記憶體溢出分為兩種情況,OutOfMemoryError和StackOverflowError。           
  • OutOfMemoryError是在程式無法申請到足夠的記憶體的時候抛出的異常。
  • StackOverflowError是線程申請的棧深度大于虛拟機所允許的深度所抛出的異常。

ERROR和Exception是有差別

Exception 和 Error 展現了 Java 平台設計者對不同異常情況的分類。           
  • Exception 是程式正常運作中,可以預料的意外情況,可能并且應該被捕獲,進行相應處理。
  • Error 是指在正常情況下,不大可能出現的情況,絕大部分的 Error 都會導緻程式(比如 JVM 自身)處于非正常的、不可恢複狀态。

OutOfMemoryError

OutOfMemoryError是在程式無法申請到足夠的記憶體的時候抛出的異常,導緻OutOfMemoryError異常的常見原因有以下幾種:

  • 記憶體中加載的資料量過于龐大,如一次從資料庫取出過多資料;
  • 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
  • 代碼中存在死循環或循環産生過多重複的對象實體;
  • 使用的第三方軟體中的BUG;
  • 啟動參數記憶體值設定的過小;

在不同的Web伺服器或程式中,此錯誤常見的錯誤提示如下:

  • tomcat: java.lang.OutOfMemoryError: PermGen space
  • tomcat: java.lang.OutOfMemoryError: Java heap space
  • weblogic: Root cause of ServletException java.lang.OutOfMemoryError
  • resin: java.lang.OutOfMemoryError
  • java: java.lang.OutOfMemoryError

OOM錯誤發生的場景很多,比如下面這段代碼,最終會發生OutOfMemoryError,為了能更快的出現錯誤,我們可以設定一下jvm中堆的最大值,設定jvm值的方法是通過-Xms(堆的最小值),-Xmx(堆的最大值)

public static void main(String[] args){
  List<UserBean> users = new ArrayList<UserBean>();
  while (true) {
     users.add(new UserBean());
  }
}           

StackOverflowError

StackOverflowError代表的是,當程式中棧深度所需空間大小,超過了虛拟機配置設定給線程的棧大小時就會出現此error。StackOverflowError發生于單個線程的棧大小無法滿足程式所需的棧空間大小時。

java棧是java虛拟機的一個重要的組成部分,在棧裡進行線程操作,存放方法參數等等。每個線程在建立時都會建立一個虛拟機棧,其内部儲存一個個的棧幀(Stack Frame),對應着一次次的 Java 方法調用。棧在初始化過後是有一定的大小的,也可通過jvm參數-Xss設定每個線程的堆棧大小。棧幀中存儲着局部變量表、操作數(operand)棧、動态連結、方法正常退出或者異常退出的定義等。

棧深度可了解為單個線程的堆棧空間最多能産生多少個棧幀,當堆棧總大小不變時,棧幀存儲的資訊越多,棧幀越大,每個線程堆棧深度越小。

記憶體溢出及解決方案什麼是記憶體溢出

以下代碼将會報StackOverflowError:

public static void test(String str){
    System.out.println(str);
     test(str);
 }           

記憶體溢出發生的區域

通常可以把 JVM 記憶體區域分為下面幾個方面,其中,有的區域是以線程為機關,而有的區域則是整個 JVM 程序為機關的。

  • Method Area(方法區)
  • Java stack(java 虛拟機棧)
  • Native MethodStack(本地方法棧)
  • Heap(堆)
  • Program Counter Regster(程式計數器)

從下圖中看出方法區和堆用黃色标記,和其他三個區域的不同點就是,方法區和堆是線程共享的,所有的運作在jvm上的程式都能通路這兩個區域,堆,方法區和虛拟機的生命周期一樣,随着虛拟機的啟動而存在,而棧和程式計數器是依賴使用者線程的啟動和結束而建立和銷毀。

記憶體溢出及解決方案什麼是記憶體溢出
  • Program Counter Regster(程式計數器):每一個使用者線程對應一個程式計數器,用來訓示目前線程所執行位元組碼的行号。由程式計數器給文字碼解釋器提供下一條要執行的位元組碼的的位置。根據jvm規範,這個區域不會發生記憶體溢出。
  • Java stack(java 虛拟機棧):這個區域是最容易出現記憶體異常的區域,每一個線程對應生成一個線程棧,線程每執行一個方法的時候,都會建立一個棧幀,用來存放方法的局部變量表,操作樹棧,動态連接配接,方法入口。jvm規範對這個區域定義了兩種記憶體異常。
    • 如果虛拟機在擴充棧時無法申請到足夠的記憶體空間則抛出OutOfMemoryError
    • 如果線程請求的棧深度大于虛拟機所允許的最大深度,将會抛出StackOverflowError
  • Native MethodStack(本地方法棧):和虛拟機棧一樣,不同的是處理的對象不一樣,虛拟機棧處理java的位元組碼,而本地棧則是處理的Native方法。其他方面一緻。
  • Heap(堆):前面說了堆是所有線程都能通路的,随着虛拟機的啟動而存在,這塊區域很大,因為所有的線程都在這個區域儲存執行個體化的對象,因為每一個類型中,每個接口實作類需要的記憶體不一樣,一個方法内的多個分支需要的記憶體也不盡相同,我們隻有在運作的時候才能知道要建立多少對象,需要配置設定多大的位址空間。GC關注的正是這樣的部分内容,是以很多時候也将堆稱為GC堆。堆中肯定不會抛出StackOverflowError類型的異常,是以隻有OutOfMemoryError相關類型的異常。
  • Method Area(方法區):用于存放已被虛拟機加載的類資訊,常量,靜态方法,即使編譯後的代碼。由于早期的 Hotspot JVM 實作,很多人習慣于将方法區稱為永久代(Permanent Generation)。這個區域隻能抛出OutOfMemoryError類型的錯誤,OutOfMemoryError: PermGen space。
  • OutOfMemoryError的類型及解決方案

    在發生OOM後需要重點排查以下幾點:

  • 檢查代碼中是否有死循環或遞歸調用。
  • 檢查是否有大循環重複産生新對象實體。

檢查對資料庫查詢中,是否有一次獲得全部資料的查詢。一般來說,如果一次取十萬條記錄到記憶體,就可能引起記憶體溢出。這個問題比較隐蔽,在上線前,資料庫中資料較少,不容易出問題,上線後,資料庫中資料多了,一次查詢就有可能引起記憶體溢出。是以對于資料庫查詢盡量采用分頁的方式查詢。

檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。

OutOfMemoryError: PermGen space

PermGen space的全稱是Permanent Generation space,是指記憶體的永久儲存區域。這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到PermGen space中,它和存放類執行個體(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程式運作期對PermGen space進行清理。

對于老版本的 Oracle JDK,因為永久代的大小是有限的,并且 JVM 對永久代垃圾回收(如,常量池回收、解除安裝不再需要的類型)非常不積極,是以當我們不斷添加新類型的時候,永久代出現 OutOfMemoryError 也非常多見,尤其是在運作時存在大量動态類型生成的場合;類似 Intern 字元串緩存占用太多空間,也會導緻 OOM 問題。對應的異常資訊,會标記出來和永久代相關:“java.lang.OutOfMemoryError: PermGen space”。           

解決方法:手動設定MaxPermSize大小修改TOMCAT_HOME/bin/catalina.sh

JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m"           

OutOfMemoryError:Java heap space

發生在堆記憶體上的記憶體溢出。原因可能有很多種,例如,可能存在記憶體洩漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的資料量,但是沒有顯式指定 JVM 堆大小或者指定數值偏小;或者出現 JVM 處理引用不及時,導緻堆積起來,記憶體無法釋放等。
           

解決方案:增加jvm的記憶體大小。其中"-Xms128M"為初始記憶體,"-Xmx256M"為最大記憶體。

-Xmx2048m -Xms2048m           

最後重要提示:

但是,對于記憶體洩漏問題,無法通過設定啟動參數的方式來解決,這種情況下增加堆記憶體大小隻會延緩OOM的出現時間,治标不治本。也不推薦一開始就将堆記憶體大小設定的很大,這樣會掩蓋測試期間可能出現的問題,導緻線上問題的出現。

對于這種情況,我們應該對程式中可能出現記憶體洩漏的地方進行優化。主要包括避免死循環,應該及時釋放種資源:記憶體, 資料庫的各種連接配接,防止一次載入太多的資料。導緻java.lang.OutOfMemoryError的根本原因是程式不健壯。是以,從根本上解決Java記憶體溢出的唯一方法就是修改程式,及時地釋放沒用的對象,釋放記憶體空間。遇到該錯誤的時候要仔細檢查程式。
           

原文連結

記憶體溢出及解決方案什麼是記憶體溢出