關於JVM內存溢出的原因分析及解決方案探討

clipboard.png

前言:JVM中除了程序計數器,其他的區域都有可能會發生內存溢出。

0.什麼是內存溢出

當程序需要申請內存的時候,由於沒有足夠的內存,此時就會拋出OutOfMemoryError,這就是內存溢出。

1.內存泄漏和內存溢出區別與聯繫

  • 內存泄漏:系統分配的內存沒有被回收。
  • 內存溢出:分配的內存空間超過系統內存。

2.內存泄漏的原因分析

clipboard.png

jvm由5大塊組成:堆,棧,本地方法棧,程序計數器,方法區。棧它的主要記錄方法的執行和對象的引用。堆則存在真正的引用的對象。

內存泄漏是由於使用不當,把一部分內存“丟掉了”,導致這部分內存不可用。

當在堆中創建了對象,後來沒有使用這個對象了,又沒有把整個對象的相關引用設爲null。此時垃圾收集器會認爲這個對象是需要的,就不會清理這部分內存。這就會導致這部分內存不可用。

所以內存泄漏會導致可用的內存減少,進而會導致內存溢出。

3. JVM垃圾回收機制思想

就是從棧出發(root),遍歷對象的引用,在遍歷堆裏面的引用對象,因爲棧中的對象的引用執行完畢就刪除,所以我們就可以通過棧中的對象的引用,查找到堆中沒有被指向的對象,這些對象即爲不可到達對象,對其進行垃圾回收。

clipboard.png

4.內存溢出的原因分析

內存溢出是由於沒被引用的對象(垃圾)過多造成JVM沒有及時回收,造成的內存溢出。如果出現這種現象可行代碼排查:

  1. 是否App中的類中和引用變量過多使用了Static修飾 如public staitc Student s;在類中的屬性中使用
    static修飾的最好只用基本類型或字符串。如public static int i = 0; //public static
    String str;
  2. 是否App中使用了大量的遞歸或無限遞歸(遞歸中用到了大量的建新的對象)
  3. 是否App中使用了大量循環或死循環(循環中用到了大量的新建的對象)
  4. 檢查App中是否使用了向數據庫查詢所有記錄的方法。即一次性全部查詢的方法,如果數據量超過10萬多條了,就可能會造成內存溢出。所以在查詢時應採用“分頁查詢”。
  5. 檢查是否有數組,List,Map中存放的是對象的引用而不是對象,因爲這些引用會讓對應的對象不能被釋放。會大量存儲在內存中。
  6. 檢查是否使用了“非字面量字符串進行+”的操作。因爲String類的內容是不可變的,每次運行"+"就會產生新的對象,如果過多會造成新String對象過多,從而導致JVM沒有及時回收而出現內存溢出。

如:

String s1 = "My name";
String s2 = "is";
String s3 = "xiaomanong";
String str = s1 + s2 + s3 +.........;

這是會容易造成內存溢出的

但是String str = "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是這種就不會造成內存溢出。因爲這是”字面量字符串“,在運行"+"時就會在編譯期間運行好。不會按照JVM來執行的。

在使用String,StringBuffer,StringBuilder時,如果是字面量字符串進行"+"時,應選用String性能更好;如果是String類進行"+"時,在不考慮線程安全時,應選用StringBuilder性能更好。

clipboard.png

5.常見的四種內存溢出情況

  1. 堆溢出(OutOfMemoryError:java heap space)
  2. 持久代溢出(OutOfMemoryError: PermGen space)
  3. 棧溢出(StackOverflowError)
  4. OutOfMemoryError:unable to create native thread

1)堆溢出:JVM Heap :java.lang.OutOfMemoryError: Java heap space

JVM在啓動的時候會自動設置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的時間是用於GC,且可用的Heap size 不足2%的時候將拋出此異常信息。

解決方法 :手動設置JVM Heap(堆)的大小。

2)持久代溢出:PermGen space : java.lang.OutOfMemoryError: PermGen space

PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域。爲什麼會內存溢出,這是由於這塊內存主要是被JVM存放Class和Meta信息的,Class在被Load的時候被放入PermGen space區域,它和存放Instance的Heap區域不同,sun的 GC不會在主程序運行期對PermGen space進行清理,所以如果你的APP會載入很多CLASS的話,就很可能出現PermGen space溢出。一般發生在程序的啓動階段。

解決方法 : 通過-XX:PermSize和-XX:MaxPermSize設置永久代大小即可。

3)棧溢出: java.lang.StackOverflowError : Thread Stack space

棧溢出了,JVM依然是採用棧式的虛擬機,這個和C和Pascal都是一樣的。函數的調用過程都體現在堆棧和退棧上了。調用構造函數的 “層”太多了,以致於把棧區溢出了。 通常來講,一般棧區遠遠小於堆區的,因爲函數調用過程往往不會多於上千層,而即便每個函數調用需要 1K的空間(這個大約相當於在一個C函數內聲明瞭256個int類型的變量),那麼棧區也不過是需要1MB的空間。通常棧的大小是1-2MB的。通俗一點講就是單線程的程序需要的內存太大了。 通常遞歸也不要遞歸的層次過多,很容易溢出。

解決方法 :1:修改程序。2:通過 -Xss: 來設置每個線程的Stack大小即可。

4)OutOfMemoryError:unable to create native thread

OutOfMemoryError:unable to create native thread:字面意思是內存溢出:無法創建新的線程。字面意思已經很明顯了,出現這種情況的原因基本下面2點:

  • 程序創建的線程數超過操作系統的限制。
  • JVM佔用的內存太多,導致創建線程的內存空間太小。

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

解決方法:1:增大進程所佔用的總內存。2:減少-Xmx或者-Xss來達到創建更多線程的目的。

5)小結

棧內存溢出:程序所要求的棧深度過大導致。
堆內存溢出: 分清 內存泄露還是 內存容量不足。泄露則看對象如何被 GC Root 引用。不足則通過 調大 -Xms,-Xmx參數。
持久帶內存溢出:Class對象未被釋放,Class對象佔用信息過多,有過多的Class對象。
無法創建本地線程:總容量不變,堆內存,非堆內存設置過大,會導致能給線程的內存不足。

補充:阿里巴巴內存溢出面試題

下面哪種情況會導致持久區jvm堆內存溢出():

A. 循環上萬次的字符串處理

B. 在一段代碼內申請上百M甚至上G的內存

C. 使用CGLib技術直接操作字節碼運行,生成大量的動態類

D. 不斷創建對象

解答:AC

解析:AC是持久帶,B直接內存也就是堆外內存,D堆內存。

參考書籍:

《深入理解Java虛擬機》 (第二版) 周志明 著;

溫馨提示: 如果你喜歡本文,並且想要學習更多幹貨內容,可以關注一下我的公衆號《Java技術zhai》;
不定期的技術乾貨內容分享,帶你重新理解架構的魅力!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章