一、背景
在實際的開發中,性能問題的分析一直是運維團隊的痛點,無論是緩慢內存溢出還是迅速的內存爆炸,對系統和業務的破壞都是快速而巨大的,此貼分享下簡單的分析內存問題的經驗。
二、相關名詞
分代:根據對象的生命周期長短,把堆分為3個代:Young,Old和Permanent,根據不同代的特點采用不同的收集算法,揚長避短也。
-
Young(年輕代)
年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor區也滿了的時候,從第一個Survivor區復制過來的並且此時還存活的對象,將被復制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor復制過來的對象。而且,Survivor區總有一個是空的。 -
Tenured(年老代)
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。 -
Perm(持久代)
用於存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設置。
GC的基本概念
gc分為full gc 跟 minor gc(Young GC也就是Minor GC),當每一塊區滿的時候都會引發gc。
-
Scavenge GC
一般情況下,當新對象生成,並且在Eden申請空間失敗時,就觸發了Scavenge GC,堆Eden區域進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。 -
Full GC
對整個堆進行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此應該盡可能減少Full GC。有如下原因可能導致Full GC:
-
上一次GC之后Heap的各域分配策略動態變化
-
System.gc()被顯示調用
-
Perm域被寫滿
-
Tenured被寫滿
內存溢出 out of memory,是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。
內存泄露 memory leak,是指程序在申請內存后,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積后果很嚴重,無論多少內存,遲早會被占光。其實說白了就是該內存空間使用完畢之后未回收。
注:此部分內容參考https://blog.csdn.net/jethai/article/details/52345074,感謝原作者
三、OOM場景模擬
由於Object對象無限增長導致的內存溢出代碼示例,以及運行結果
public class OutofMemeorySample { public static voidmain(String[] args) { headOutOfMemory(); } static void headOutOfMemory(){ long count= 0; try { List<Object> objects = newArrayList<Object>(); while (true) { count++; objects.add(new Object()); } } catch(Throwable ex) { System.out.println(count); ex.printStackTrace(); } } }
找出運行中的進程id,並生成dump文件,該部分操作詳細請見https://www.cnblogs.com/jiyukai/p/9292102.html
在JDK自帶的分析工具jvisualvm中裝入dump文件,通過工具分析堆棧內存情況
在jvisualvm中可以查看到dump的基本信息,類實例及占比等情況
四、其他案例
本地設置的最大堆內存是800M左右,代碼里面是往一個List里面瘋狂加數據,直到撐爆堆內存,得到的監控內容是這樣的:
分析:紅框框出的部分是發生堆內存溢出時的情形,已使用的堆大小(藍色部分)並沒有增長特別明顯,但是申請的堆的大小(黃色部分)從默認的400多兆急速上漲,漲到800M,然后內存溢出,然而使用的堆大小依然沒怎么增長。
所以,dump文件中的實例列表其實是反映了使用的堆的情況,而使用的堆內存並沒有達到預先設置的最大堆內存,只是在申請堆內存的過程中超出了預先設置的最大堆內存,然后內存溢
此處參考:https://blog.csdn.net/lkforce/article/details/60878295,感謝原作者