Java堆內存理解

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)去加載的辦法也是一個思路,但前提是容器裏部署了多個應用,且這些應用有大量的共享類庫
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章