http://www.importnew.com/14630.html
java堆的特色
《深刻理解java虛擬機》是什麼描述java堆的html
----------------------------------------------------------------java
參考文獻:
http://www.importnew.com/14630.html Java 堆內存
http://blog.csdn.net/ylyg050518/article/details/52244994 Java虛擬機(二)——Java堆內存劃分算法
堆內存劃分:數組
堆的垃圾回收方式
java堆是GC垃圾回收的主要區域。 GC分爲兩種: Minor GC、Full GC(也叫作Major GC).
Minor GC(簡稱GC)
Minor GC是發生在新生代中的垃圾收集動做, 所採用的是複製算法。
GC通常爲堆空間某個區發生了垃圾回收,
新生代(Young)幾乎是全部java對象出生的地方。即java對象申請的內存以及存放都是在這個地方。java中的大部分對象一般不會長久的存活, 具備朝生夕死的特色。
當一個對象被斷定爲「死亡」的時候, GC就有責任來回收掉這部分對象的內存空間。
新生代是收集垃圾的頻繁區域。
回收過程以下:
當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在通過一次 Minor GC 後,若是對象還存活,而且可以被另一塊 Survivor 區域所容納(上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor 區域 ( 即 to 區域 ) 中,而後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成爲老年代。
但這也不是必定的,對於一些較大的對象 ( 即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
Full GC
Full GC 基本都是整個堆空間及持久代發生了垃圾回收,所採用的是標記-清除算法。
現實的生活中,老年代的人一般會比新生代的人 「早死」。堆內存中的老年代(Old)不一樣於這個,老年代裏面的對象幾乎個個都是在 Survivor 區域中熬過來的,它們是不會那麼容易就 「死掉」 了的。所以,Full GC 發生的次數不會有 Minor GC 那麼頻繁,而且作一次 Full GC 要比進行一次 Minor GC 的時間更長,通常是Minor GC的 10倍以上。
另外,標記-清除算法收集垃圾的時候會產生許多的內存碎片 ( 即不連續的內存空間 ),此後須要爲較大的對象分配內存空間時,若沒法找到足夠的連續的內存空間,就會提早觸發一次 GC 的收集動做
---------------------------------------------------------------------
下面咱們來分析一下GC日誌:
如何在eclipse中打印GC日誌? 參考這篇文章: 如何在eclipse中打印gc日誌.
咱們首先寫一段代碼:
public static void main(String[] args) {
Object obj = new Object();
System.gc();
System.out.println();
obj = new Object();
obj = new Object();
System.gc();
System.out.println();
}
在Run as - Run Configuration中配置參數,使得控制檯可以顯示 GC 相關的日誌信息,執行上面代碼,下面是其中一次執行的結果。eclipse
-verbose:gc
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetailsjvm
--------------------------------------------------------------
再看下面這個圖:
第一次System.gc()spa
第二次執行System.gc().net
對比這兩次gc的結果:
從Full GC中能夠看出, 新生代的可用內存約爲38M, 老年代可用內存約爲86M, 堆的可用總內存約爲124M.
能夠看出>新生代內存佔用jvm堆內存的1/3, 老年代內存佔用jvm堆總內存的2/3. GC堆新生代內存回收比較樂觀. 對老年代,以及方法區的回收並不明顯, 或者說不如新生代.
除此以外:
再來觀察: 第一次gc, 新生代回收狀況是665K->648K, 而老年代將新生代的648K回收至0K. 放入了老年代, 因此老年代是從0K增長到470K. 因此,新生代回收內存狀況是665K->648K, 老年代回收狀況是648K->470K.
第二次gc看出, Full GC處理時間是GC的17倍.
--------------------------------------------------------------
JVM參數選項
下面只列舉其中的幾個經常使用和容易掌握的配置選項:線程
--------------------------------------------------------------日誌
咱們再來分析一個案例:
------------源代碼 開始------------
public static void main(String[] args) {
new Test2().doTest();
}
public void doTest() {
Integer M = new Integer(1024 * 1024 * 1); // 單位, 兆(M)
byte[] bytes = new byte[1 * M]; // 申請 1M 大小的內存空間
bytes = null; // 斷開引用鏈
System.gc(); // 通知 GC 收集垃圾
System.out.println();
bytes = new byte[1 * M]; // 從新申請 1M 大小的內存空間
bytes = new byte[1 * M]; // 再次申請 1M 大小的內存空間
System.gc();
System.out.println();
}
------------源代碼 結束------------
執行結果:
從打印結果能夠看出
1. 新生代內存空間約爲38M, 其中eden空間約爲33M, from Survivor 空間約爲5M, to Survivor空間約爲5M.
2. 這裏咱們設置的-Xmn爲43M, 也就是說指定的新生代的空間是43M, 那爲何打印結果顯示的時38M呢?另外的5M哪裏去了?
實際上是這樣的: 新生代 = Eden + From Survivor + To Survivor = 33 + 5+ 5 = 43M. 可見新生代的內存空間確實是43M, 按照Xmn分配得來的.
3. 並且這裏指定了SurvivorRatio = 8. 所以, eden = 8/10 的新生代空間 = 8/10 * 43 = 38M。
from = to = 1/10 的新生代空間 = 1/10 * 43 = 5M。
4. 堆信息中新生代的 total 18432K 是這樣來的: eden + 1 個 survivor = 33K + 5K = 18432K,即約爲 38M。
5. 由於 jvm 每次只是用新生代中的 eden 和 一個 survivor,所以新生代實際的可用內存空間大小爲所指定的 90%。
所以能夠知道,這裏新生代的內存空間指的是新生代可用的總的內存空間,而不是指整個新生代的空間大小。
6. 另外,能夠看出老年代的內存空間爲 86016K ( 約 86M ),堆大小 = 新生代 + 老年代。所以在這裏,老年代 = 堆大小 – 新生代 = 124 – 38 = 86M。(注: 這裏應該是減去43的, 可我這裏減去43之後,就不是86了, 多是不能版本的虛擬機, 細節不同)
7. 最後,這裏還指定了 PermSize = 21m,PermGen 即永久代 ( 方法區 ),它還有一個名字,叫非堆,主要用來存儲由 jvm 加載的類文件信息、常量、靜態變量等。
-----------------------------------------------------------------
打個盹,回到 doTest() 方法中,能夠看到代碼在第 1七、2一、22 這三行中分別申請了一塊 1M 大小的內存空間,並在 19 和 23 這兩行中分別顯式的調用了 System.gc()。從控制檯打印的信息來看,每次調 System.gc(),是先進行 Minor GC,而後再進行 Full GC。
第 19 行觸發的 Minor GC 收集分析:
從信息 PSYoungGen : 1689K -> 632K,能夠知道,在第 17 行爲 bytes 分配的內存空間已經被回收完成。
引發 GC 回收這 1M 內存空間的因素是第 18 行的 bytes = null; bytes 爲 null 代表以前申請的那 1M 大小的內存空間如今已經沒有任何引用變量在使用它了,而且在內存中它處於一種不可到達狀態 ( 即沒有任何引用鏈與 GC Roots 相連 )。那麼,當 Minor GC 發生的時候,GC 就會來回收掉這部分的內存空間。
第 19 行觸發的 Full GC 收集分析:
在 Minor GC 的時候,信息顯示 PSYoungGen : 1689K -> 632K,再看看 Full GC 中顯示的 PSYoungGen : 632K -> 0K,能夠看出,Full GC 後,新生代的內存使用變成0K 了
剛剛說到 Full GC 後,新生代的內存使用從 632K 變成 0K 了,那麼這 632K 到底哪去了 ? 難道都被 GC 當成垃圾回收掉了 ? 固然不是了。咱們在 main 方法中 new 了一個 Test 類的實例,這裏的 Test 類的實例屬於對象,它應該被分配到新生代內存當中,如今還在調用這個實例的 doTest 方法呢,GC 不可能在這個時候來回收它的。
接着往下看 Full GC 的信息,會發現一個頗有趣的現象,PSOldGen: 0K -> 470K,能夠看到,Full GC 後,老年代的內存使用從 0K 變成了 160K,想必你已經猜到大概是怎麼回事了。當 Full GC 進行的時候,默認的方式是儘可能清空新生代 ( YoungGen ),所以在調 System.gc() 時,新生代 ( YoungGen ) 中存活的對象會提早進入老年代。
第 23 行觸發的 Minor GC 收集分析:
從信息 PSYoungGen : 4045K -> 1088K,能夠知道,在第 21 行建立的,大小爲 1M 的數組被 GC 回收了。在第 22 行建立的,大小也爲 1M 的數組因爲 bytes 引用變量還在引用它,所以,它暫時未被 GC 回收。
第 23 行觸發的 Full GC 收集分析:
在 Minor GC 的時候,信息顯示 PSYoungGen : 4045K -> 1088K,Full GC 中顯示的 PSYoungGen : 1088K -> 0K,以及 PSOldGen: 470K -> 1494K,能夠知道,新生代 ( YoungGen ) 中存活的對象又提早進入老年代了。