堆記憶體設定
原理
JVM堆記憶體分為2塊:Permanent Space 和 Heap Space。
- Permanent 即 持久代(Permanent Generation),主要存放的是Java類定義資訊,與垃圾收集器要收集的Java對象關系不大。
- Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年輕代(Young Generation)。年老代和年輕代的劃分對垃圾收集影響比較大。年輕代
所有新生成的對象首先都是放在年輕代。年輕代的目标就是盡可能快速的收集掉那些生命周期短的對象。年輕代一般分3個區,1個Eden區,2個Survivor區(from 和 to)。
大部分對象在Eden區中生成。當Eden區滿時,還存活的對象将被複制到Survivor區(兩個中的一個),當一個Survivor區滿時,此區的存活對象将被複制到另外一個Survivor區,當另一個Survivor區也滿了的時候,從前一個Survivor區複制過來的并且此時還存活的對象,将可能被複制到年老代。
2個Survivor區是對稱的,沒有先後關系,是以同一個Survivor區中可能同時存在從Eden區複制過來對象,和從另一個Survivor區複制過來的對象;而複制到年老區的隻有從另一個Survivor區過來的對象。而且,因為需要交換的原因,Survivor區至少有一個是空的。特殊的情況下,根據程式需要,Survivor區是可以配置為多個的(多于2個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。
針對年輕代的垃圾回收即 Young GC。
年老代
在年輕代中經曆了N次(可配置)垃圾回收後仍然存活的對象,就會被複制到年老代中。是以,可以認為年老代中存放的都是一些生命周期較長的對象。
針對年老代的垃圾回收即 Full GC。
持久代
用于存放靜态類型資料,如 Java Class, Method 等。持久代對垃圾回收沒有顯著影響。但是有些應用可能動态生成或調用一些Class,例如 Hibernate CGLib 等,在這種時候往往需要設定一個比較大的持久代空間來存放這些運作過程中動态增加的類型。
是以,當一組對象生成時,記憶體申請過程如下:
- JVM會試圖為相關Java對象在年輕代的Eden區中初始化一塊記憶體區域。
- 當Eden區空間足夠時,記憶體申請結束。否則執行下一步。
- JVM試圖釋放在Eden區中所有不活躍的對象(Young GC)。釋放後若Eden空間仍然不足以放入新對象,JVM則試圖将部分Eden區中活躍對象放入Survivor區。
- Survivor區被用來作為Eden區及年老代的中間交換區域。當年老代空間足夠時,Survivor區中存活了一定次數的對象會被移到年老代。
- 當年老代空間不夠時,JVM會在年老代進行完全的垃圾回收(Full GC)。
- Full GC後,若Survivor區及年老代仍然無法存放從Eden區複制過來的對象,則會導緻JVM無法在Eden區為新生成的對象申請記憶體,即出現“Out of Memory”。
OOM(“Out of Memory”)異常一般主要有如下2種原因:
1. 年老代溢出,表現為:java.lang.OutOfMemoryError:Javaheapspace 這是最常見的情況,産生的原因可能是:設定的記憶體參數Xmx過小或程式的記憶體洩露及使用不當問題。 例如循環上萬次的字元串處理、建立上千萬個對象、在一段代碼内申請上百M甚至上G的記憶體。還有的時候雖然不會報記憶體溢出,卻會使系統不間斷的垃圾回收,也無法處理其它請求。這種情況下除了檢查程式、列印堆記憶體等方法排查,還可以借助一些記憶體分析工具,比如MAT就很不錯。
2. 持久代溢出,表現為:java.lang.OutOfMemoryError:PermGenspace 通常由于持久代設定過小,動态加載了大量Java類而導緻溢出 ,解決辦法唯有将參數 -XX:MaxPermSize 調大(一般256m能滿足絕大多數應用程式需求)。将部分Java類放到容器共享區(例如Tomcat share lib)去加載的辦法也是一個思路,但前提是容器裡部署了多個應用,且這些應用有大量的共享類庫