Java常見內存溢出異常分析

2021-01-11 電子發燒友
  Java 堆 OutOfMemoryError

  Java 堆是用來存儲對象實例的, 因此如果我們不斷地創建對象, 並且保證 GC Root 和創建的對象之間有可達路徑以免對象被垃圾回收, 那麼當創建的對象過多時, 會導致 heap 內存不足, 進而引發 OutOfMemoryError 異常。

  public class OutOfMemoryErrorTest{

  public static void main (String[] args){

  List《Integer》 list = new ArryList《》();

  int i=0;

  while(true){

  list.add(i++);

  }}}

  上面是一個引發 OutOfMemoryError 異常的代碼, 我們可以看到, 它就是通過不斷地創建對象, 並將對象保存在 list 中防止其被垃圾回收, 因此當對象過多時, 就會使堆內存溢出。

  通過 java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 我們設置了堆內存為 10 兆, 並且使用參數 -XX:+HeapDumpOnOutOfMemoryError 讓 JVM 在發生 OutOfMemoryError 異常時列印出當前的內存快照以便於後續分析。

  編譯運行上述代碼後, 會有如下輸出:

  》》》java -Xms10m - Xms10m-XX:HeapDumpOnOutOfMemoryError com.test.OutOfMemoryErrorTest 16-10-12 10:28

  java.lang.OutOfMemoryError:Java heap space

  Dumping heap to java_pid1810.hprof.。。

  Heap dump file created [14212861 bytes in 0.128 secs]

  Exception in thread 「main」 java.lang.OutOfMemoryError:Java heap space

  at java.util.Arrays.copyof(Arrays.java:3210)

  at java.util.Arrays.copyof(Arrays.java:3181)

  堆內存溢出的時候,虛擬機會拋出java.lang.OutOfMemoryError:java heap space,出現此種情況的時候,我們需要根據內存溢出的時候產生的dump文件來具體分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm啟動參數)。出現此種問題的時候有可能是內存洩露,也有可能是內存溢出了。

  如果內存洩露,我們要找出洩露的對象是怎麼被GC ROOT引用起來,然後通過引用鏈來具體分析洩露的原因。

  如果出現了內存溢出問題,這往往是程序本生需要的內存大於了我們給虛擬機配置的內存,這種情況下,我們可以採用調大-Xmx來解決這種問題。

  下面我們通過如下的代碼來演示一下此種情況的溢出:

  [java] view plain copyimport 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]);

  }

  }

  我們通過如下的命令運行上面的代碼:

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

  程序輸入如下的信息:

  [plain] view plain copy[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操作也不會出現異常了。

  Java 棧 StackOverflowError

  我們知道, JVM 的運行時數據區中有一個叫做 虛擬機棧 的內存區域, 此區域的作用是: 每個方法在執行時都會創建一個棧幀, 用於存儲局部變量表, 操作數棧, 方法出口等信息。

  因此我們可以創建一個無限遞歸的遞歸調用, 當遞歸深度過大時, 就會耗盡棧空間, 進而導致了 StackOverflowError 異常。

  下面是具體的代碼:

  public class OutOfMemoryErrorTest{

  public static void main (String [] srgs){

  stackOutOfMemoryError(1);

  )

  public static void stackOutOfMemoryError(int depth){

  depth++;

  stackOutOfMemoryError(depth);

  }}

  當編譯運行上述的代碼後, 會輸出如下異常信息:

  Exception in thread 「main」 java.lang.StackOverflowError

  at com.test.OutOfMemoryErrorTest.stackOutOfMemoryError(OutOfMemoryErrorTest.java:27)

  棧溢出拋出java.lang.StackOverflowError錯誤,出現此種情況是因為方法運行的時候棧的深度超過了虛擬機容許的最大深度所致。

  出現這種情況,一般情況下是程序錯誤所致的,比如寫了一個死遞歸,就有可能造成此種情況。 下面我們通過一段代碼來模擬一下此種情況的內存溢出。

  [java] view plain copyimport java.util.*;

  import java.lang.*;

  public class OOMTest{

  public void stackOverFlowMethod(){

  stackOverFlowMethod();

  }

  public static void main(String.。。 args){

  OOMTest oom = new OOMTest();

  oom.stackOverFlowMethod();

  }

  }

  運行上面的代碼,會拋出如下的異常:

  [plain] view plain copyException in thread 「main」 java.lang.StackOverflowError

  at OOMTest.stackOverFlowMethod(OOMTest.java:6)

  通過上面的實驗其實也從側面驗證了一個結論:當對象大於新生代剩餘內存的時候,將直接放入老年代,當老年代剩餘內存還是無法放下的時候,出發垃圾收集,收集後還是不能放下就會拋出內存溢出異常了

  持久帶溢出(OutOfMemoryError: PermGen space)

  我們知道Hotspot jvm通過持久帶實現了Java虛擬機規範中的方法區,而運行時的常量池就是保存在方法區中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息佔用的內存超過了我們配置。當持久帶溢出的時候拋出java.lang.OutOfMemoryError: PermGen space。

  我在工作可能在如下幾種場景下出現此問題。

  使用一些應用伺服器的熱部署的時候,我們就會遇到熱部署幾次以後發現內存溢出了,這種情況就是因為每次熱部署的後,原來的class沒有被卸載掉。

  如果應用程式本身比較大,涉及的類庫比較多,但是我們分配給持久帶的內存(通過-XX:PermSize和-XX:MaxPermSize來設置)比較小的時候也可能出現此種問題。

  一些第三方框架,比如spring,hibernate都通過字節碼生成技術(比如CGLib)來實現一些增強的功能,這種情況可能需要更大的方法區來存儲動態生成的Class文件。

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

  [java] view plain copyimport 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

  運行後的輸入如下圖所示:

  [plain] view plain copyException 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這種錯誤。 出現這種情況的時候,一般是下面兩種情況導致的:

  程序創建的線程數超過了作業系統的限制。對於Linux系統,我們可以通過ulimit -u來查看此限制。

  給虛擬機分配的內存過大,導致創建線程的時候需要的native內存太少。我們都知道作業系統對每個進程的內存是有限制的,我們啟動Jvm,相當於啟動了一個進程,假如我們一個進程佔用了4G的內存,那麼通過下面的公式計算出來的剩餘內存就是建立線程棧的時候可以用的內存。 線程棧總可用內存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序計數器佔用的內存 通過上面的公式我們可以看出,-Xmx 和 MaxPermSize的值越大,那麼留給線程棧可用的空間就越小,在-Xss參數配置的棧容量不變的情況下,可以創建的線程數也就越小。因此如果是因為這種情況導致的unable to create native thread,那麼要麼我們增大進程所佔用的總內存,或者減少-Xmx或者-Xss來達到創建更多線程的目的。

  JVM內存區域組成

  簡單的說java中的堆和棧

  java把內存分兩種:一種是棧內存,另一種是堆內存

  1。在函數中定義的基本類型變量和對象的引用變量都在函數的棧內存中分配;

  2。堆內存用來存放由new創建的對象和數組

  在函數(代碼塊)中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域後,java會自動釋放掉為該變量所分配的內存空間;在堆中分配的內存由java虛擬機的自動垃圾回收器來管理

  堆的優勢是可以動態分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的。缺點就是要在運行時動態分配內存,存取速度較慢;

  棧的優勢是存取速度比堆要快,缺點是存在棧中的數據大小與生存期必須是確定的無靈活性。

  java堆分為三個區:New、Old和Permanent

  GC有兩個線程:

  新創建的對象被分配到New區,當該區被填滿時會被GC輔助線程移到Old區,當Old區也填滿了會觸發GC主線程遍歷堆內存裡的所有對象。Old區的大小等於Xmx減去-Xmn

  java棧存放

  棧調整:參數有+UseDefaultStackSize -Xss256K,表示每個線程可申請256k的棧空間

  每個線程都有他自己的Stack

打開APP閱讀更多精彩內容

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容圖片侵權或者其他問題,請聯繫本站作侵刪。 侵權投訴

相關焦點

  • Java進行內存洩露 GC 分析都有哪些常用好用的工具
    使用Java語言開發應用程式,雖然JVM幫我們進行了GC收集、清除工作;但是使用不當的話,還是會導致某些對象常駐堆空間無法給垃圾收集器清除,導致內存洩露、內存溢出等情況,今天盤點一下在項目中進行內存洩露分析和GC分析的一些常用、好用的工具。
  • 2020年Java基礎高頻面試題匯總
    >一般的異常信息:java.lang.OutOfMemoryError:Java heap spacess。java堆用於存儲對象實例,我們只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。
  • JAVA專業術語面試100問
    4、java有哪些基本數據類型?5、數組有沒有length()方法?String有沒有length()方法?數組沒有length()方法,它有length屬性。String有length()方法。16、簡單介紹Java異常框架?Error與Exception有什麼區別?17、java中的throw 和 throws關鍵字有什麼區別?18、列舉幾個你了解的幾個常見的運行時異常?
  • 為什麼不推薦使用try-catch-finally處理java異常?
    這篇文章是我近期看了《Effective java》一書中總結的,來自其中第九條。為了對其理解的更加透徹,因此重新分析了一下,並加入了一些其他點。finalizer機制可以關閉,但是其執行性不可預測,還有可能造成內存洩漏,所以一般不使用,雖然java9還提出了cleaner機制代替了finalizer機制,但是其執行依然不可預測,因此選擇就落在了try-catch-finally和try-with-resources之間。
  • 深入分析java中的多態(從jvm角度分析)
    對於java中多態概念的理解一直是面試常問的問題,所以今天花了一些時間好好地整理了一下,力求從java虛擬機的角度來分析和理解多態。一、認識多態1、方法調用在Java中,方法調用有兩類,動態方法調用與靜態方法調用。
  • 系統內存佔用較高?
    因此OOM溢出是必然的。; }}控制臺輸出測試結果分析這個測試驗證了前面關於線程異常終止的結論:線程執行中拋出Throwable對象且不被顯式捕獲,JVM會終止線程。調用該線程對象的interrupt方法thread.interrupt(),interrupt標識位最早被OBS底層用到的java.util.concurrent. CountDownLatch類的await()方法捕獲到,重置標識位並拋出異常,然後在一層層往上拋的時候被轉變成了別的異常類型,而且不能根據最終拋的異常類型去判斷是否是由於我們手動終止job引起的。
  • 跟我學java編程—認識java的整數類型
    示例1: 整型常量聲明:當整型數據數值大小超出了可以表示的範圍,而程序中又沒有做數值範圍的檢查時,這個整型變量所輸出的值將發生紊亂,且不是預期的運行結果,這種現象稱為溢出。示例2:int類型的溢出在D盤Java目錄下,新建「OverFlow.java」文件。
  • 萬字概覽 Java 虛擬機
    但是要實現棧上分配是非常複雜的,涉及到「逃逸分析」和「標量替換」兩項技術。逃逸分析簡單來說就是判斷一個對象的存活範圍是否有可能越出當前方法的執行範圍,也就是說當前棧幀出棧的時候這個對象是否還會存活。如果分析結果是不會存活,那麼這個對象就可以安全的分配在棧上,隨著棧幀的出棧而被銷毀。標量替換是基於逃逸分析技術的。
  • 異常—詳解
    1)異常定義異常是指在程序在執行過程中,JVM的非正常停止,Java中異常本身就是一個類,產生異常意味著創建並拋出了一個異常對象 ,JVM處理異常的方法就是終止程序。2)異常體系Exception:編譯期異常,進行編譯(寫代碼)java程序出現的問題手搓RuntimeException:運行期異常,java程序運行過程中出現的問題JVM
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口 加載階段完成後,這些二進位字節流按照虛擬機所需的格式存儲在方法區之中。
  • 提升java編程性能優化知識 程式設計師必看這幾點
    19.ArrayList & LinkedList  一個是線性表,一個是鍊表,一句話,隨機查詢儘量使用ArrayList,ArrayList優於LinkedList,LinkedList還要移動指針,添加刪除的操作LinkedList優於ArrayList,ArrayList還要移動數據,不過這是理論性分析,事實未必如此,重要的是理解好
  • Java NIO 基礎知識
    簡單分析下上圖。應用程式發出讀操作後,內核向磁碟控制器發送命令,要求磁碟返回相應數據,磁碟控制器通過 DMA 直接將數據發送到內核緩衝區。一旦內核緩衝區滿了,內核即把數據拷貝到請求數據的進程指定的緩衝區中。DMA: Direct Memory AccessWikipedia:直接內存訪問是計算機科學中的一種內存訪問技術。
  • JAVA系列-GC
    OOM1) 常見OOM異常類型(1) StackOverFlowError棧內存溢出,用於深度方法調用(循環遞歸);(2) OutOfMemoryError:Java heap space用於變量申請的空間大於jvm的最大值;(3) OutOfMemoryError
  • Yarn 的內存分配管理機制及相關參數配置
    RM1和 RM2 之間l AM2 最好是 AM1 的兩倍l 這兩個值可以在啟動時改變AM3:mapreduce.map.java.opts=-Xmx 1G #運行map任務的jvm參數,如-Xmx,-Xms等選項AM4:mapreduce.reduce.java.opts=-Xmx 2G #運行reduce任務的jvm參數,如-Xmx,-Xms等選項l AM3 必須小於 AM1,AM4 必須小於
  • Java編程中基礎反射詳細解析
    類加載指的是將類的class文件讀入內存中,並為之創建一個 java.lang.Class對象,也就是說程序使用任何類的時候,都會為其創建一個class對象。>類加載器類加載器負責將.class文件加載到內存中,並為之生成對應的Class對象。
  • 一文搞懂WeakHashMap工作原理(java後端面試高薪必備知識點)
    這個問題是一個高頻面試題,本篇文章將從概念、原理、實際使用的角度來分析。希望對你有幫助:一、什麼是WeakHashMap?即使當前內存空間不足,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤,使程序異常終止。比如String str = "hello"這時候str就是一個強引用。
  • 2018年阿里巴巴關於Java重要開源項目匯總
    JStorm 可以看作是 storm 的 java 增強版本,除了內核用純java實現外,還包括了thrift、python、facet ui。從架構上看,其本質是一個基於 zk 的分布式調度系統。地址:https://github.com/alibaba/simpleimage11. redis 的 java 客戶端 TedisTedis 是另一個 redis 的 java 客戶端。Tedis 的目標是打造一個可在生產環境直接使用的高可用 Redis 解決方案。
  • 「JAVA」萬字長篇詳述字節碼對象與反射機制完成動態編程
    ,便會啟動JVM,將字節碼文件加載到JVM中,然後開始運行;當運行java命令時,該命令將會啟動一個JVM進程,在這個JVM進程中,會保存有該JVM創建的所有線程、變量、對象,這些線程、變量、對象會共享該JVM的內存區域。
  • 開發崗位這麼多,為什麼選Java?你學Java了嗎-開課吧
    提到C++語言,很多人發現在使用過程中最容易出現的錯誤就是內存管理,而java有自動垃圾回收器,不用擔心內存。java工程師工資一般多少?java自學容易嗎?java難學嗎?學java要學多久-開課吧零基礎學java難麼?
  • 做java的你,這些英文單詞都掌握了嗎?
    15、Class variable 類變量:見靜態變量Static variable16、Collection 容器類:容器類可以看作是一種可以儲存其他對象的對象,常見的容器類有Hashtables和Vectors17、Collection interface 容器類接口:容器類接口定義了一個對所有容器類的公共接口。