堆內存的結構:算法
垃圾收集器就是垃圾收集算法的具體實現了。不一樣虛擬機所提供的垃圾收集器可能會有很大差異,咱們使用的是HotSpot,HotSpot這個虛擬機所包含的全部收集器如圖:多線程
上圖展現 了7種做用於不一樣分代的收集器,若是兩個收集器之間存在連線,那說明它們能夠搭配使用。虛擬機所處的區域說明它是屬於新生代收集器仍是老年代收集器。多說 一句,咱們必須姚明帶一個道理:沒有最好的垃圾收集器,更加沒有萬能的收集器,只能選擇對具體應用最合適的收集器。這也是HotSpot爲何要實現這麼 多收集器的緣由。OK,下面一個一個看一下收集器:併發
一、Serial收集器佈局
最基本、發展歷史最久的收集器,這個收集器是一個採用複製算法的單線程的收集器,單線程一方面意味着它只會使用一個CPU或一條線程去完成垃圾收集工做,另外一方面也意味着它進行垃圾收集時必須暫停其餘線程的全部工做,直到它收集結束爲止。後者意味着,在用戶不可見的狀況下要把用戶正常工做的線程所有停掉,這對不少應用是難以接受的。不過實際上到目前爲止,Serial收集器依然是虛擬機運行在Client模式下的默認新生代收集器,由於它簡單而高效。用戶桌面應用場景中,分配給虛擬機管理的內存通常來講不會很大,收集幾十兆甚至一兩百兆的新生代停頓時間在幾十毫秒最多一百毫秒,只要不是頻繁發生,這點停頓是徹底能夠接受的。post
二、ParNew收集器性能
ParNew收集器其實就是Serial收集器的多線程版本, 除了使用多條線程進行垃圾收集外,其他行爲和Serial收集器徹底同樣,包括使用的也是複製算法。ParNew收集器除了多線程之外和Serial收集 器並無太多創新的地方,可是它倒是Server模式下的虛擬機首選的新生代收集器,其中有一個很重要的和性能無關的緣由是,除了Serial收集器外, 目前只有它能與CMS收集器配合工做(看圖)。CMS收集器是一款幾乎能夠認爲有劃時代意義的垃圾收集器,由於它第一次實現了讓垃圾收集線程與用戶線程基 本上同時工做。ParNew收集器在單CPU的環境中絕對不會有比Serial收集器更好的效果,甚至因爲線程交互的開銷,該收集器在兩個CPU的環境中 都不能百分之百保證能夠超越Serial收集器。固然,隨着可用CPU數量的增長,它對於GC時系統資源的有效利用仍是頗有好處的。它默認開啓的收集線程 數與CPU數量相同,在CPU數量很是多的狀況下,可使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。優化
三、Parallel收集器網站
Parallel收集器也是一個新生代收集器,也是用複製算法的收集器,也是並行的多線程收集器,可是它的特色是它的關注點和其餘收集器不一樣。介紹這個收集器主要仍是介紹吞吐量的概念。CMS等收集器的關注點是儘量縮短垃圾收集時用戶線程的停頓時間,而Parallel收集器的目標則是打到一個可控制的吞吐量。所謂吞吐量的意思就是CPU用於運行用戶代碼時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總運行100分鐘,垃圾收集1分鐘,那吞吐量就是99%。另外,Parallel收集器是虛擬機運行在Server模式下的默認垃圾收集器。spa
停頓時間短適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗;高吞吐量則能夠高效率利用CPU時間,儘快完成運算任務,主要適合在後臺運算而不須要太多交互的任務。線程
虛擬機提 供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio兩個參數來精確控制最大垃圾收集停頓時間和吞吐量大小。不過不要覺得前者 越小越好,GC停頓時間的縮短是以犧牲吞吐量和新生代空間換取的。因爲與吞吐量關係密切,Parallel收集器也被稱爲「吞吐量優先收集器」。 Parallel收集器有一個-XX:+UseAdaptiveSizePolicy參數,這是一個開關參數,這個參數打開以後,就不須要手動指定新生代 大小、Eden區和Survivor參數等細節參數了,虛擬機會根據當親系統的運行狀況手機性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最 大的吞吐量。若是對於垃圾收集器運做原理不太瞭解,以致於在優化比較困難的時候,使用Parallel收集器配合自適應調節策略,把內存管理的調優任務交給虛擬機去完成將是一個不錯的選擇。
四、Serial Old收集器
Serial收集器的老年代版本,一樣是一個單線程收集器,使用「標記-整理算法」,這個收集器的主要意義也是在於給Client模式下的虛擬機使用。
五、Parallel Old收集器
Parallel收集器的老年代版本,使用多線程和「標記-整理」算法。這個收集器在JDK 1.6以後的出現,「吞吐量優先收集器」終於有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel收集器+Parallel Old收集器的組合。
六、CMS收集器
CMS收集器是一種以獲取最短回收停頓時間爲目標的老年代收集器。目前很大一部分Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤爲注重服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗,CMS收集器就很是符合這類應用的需求。CMS收集器從名字就能看出是基於「標記-清除」算法實現的。
七、G1收集器
G1(Garbage- First)收集器是當今收集器技術發展的最前沿成果之一,JDK 7 Update 4後開始進入商用。在G1收集器以前的其餘收集器進行收集的範圍都是整個新生代或者老年代,而G1收集器再也不是這樣,使用G1收集器時,Java堆的內存 佈局就與其餘收集器有很大差異,它將整個Java堆分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不 再是物理隔離的了,它們都是一部分Region的集合。G1收集器跟蹤各個Region裏面的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據容許 的收集時間,優先回收價值最大的Region(這也是Garbage-First名稱的由來)。這種使用Region劃份內存空間以及有優先級的區域回收 方式,保證了G1收集器在有限的時間內能夠獲取儘量高的收集效率。
垃圾收集器總結
來看一下對垃圾收集器的總結,列了一張表
GC組合 |
Minor GC |
Full GC |
描述 |
-XX:+UseSerialGC | Serial收集器串行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定Serial收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParNewGC | ParNew收集器並行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定ParNew收集器+Serilal Old組合執行內存回收 |
-XX:+UseParallelGC | Parallel收集器並行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定Parallel收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParallelOldGC | Parallel收集器並行回收 | Parallel Old收集器並行回收 | 該選項能夠手動指定Parallel收集器+Parallel Old收集器組合執行內存回收 |
-XX:+UseConcMarkSweepGC | ParNew收集器並行回收 | 缺省使用CMS收集器併發回收,備用採用Serial Old收集器串行回收 |
該選項能夠手動指定ParNew收集器+CMS收集 器+Serial Old收集器組合執行內存回收。優先使用ParNew收集器+CMS收集器的組合,當出現ConcurrentMode Fail或者Promotion Failed時,則採用ParNew收集器+Serial Old收集器的組合 |
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC |
Serial收集器串行回收 | ||
-XX:+UseG1GC | G1收集器併發、並行執行內存回收 | 暫無 |
GC日誌
每種收集器的日誌形式都是由它們自身的實現所決定的,換言之,每種收集器的日誌格式均可以不同。不過虛擬機爲了方便用戶閱讀,將各個收集器的日誌都維持了必定的共性,就以最前面的對象間相互引用的那個類ReferenceCountingGC的代碼爲例:
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseSerialGC」,使用Serial+Serial Old組合進行垃圾回收的日誌
[GC [DefNew: 310K->194K(2368K), 0.0269163 secs] 310K->194K(7680K), 0.0269513 secs]
[Times: user=0.00 sys=0.00, real=0.03 secs] [GC [DefNew: 2242K->0K(2368K), 0.0018814 secs] 2242K->2241K(7680K), 0.0019172 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [Tenured: 2241K->193K(5312K), 0.0056517 secs] 4289K->193K(7680K),
[Perm : 2950K->2950K(21248K)], 0.0057094 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 2432K, used 43K [0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000) eden space 2176K, 2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000) from space 256K, 0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000) to space 256K, 0% used [0x0000000005500000, 0x0000000005500000, 0x0000000005540000) tenured generation total 5312K, used 193K [0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000) the space 5312K, 3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800, 0x00000000073d0000) compacting perm gen total 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000, 0x000000000faa0000) the space 21248K, 14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00, 0x000000000bb60000) No shared spaces configured.
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseParNewGC」,使用ParNew+Serial Old組合進行垃圾回收的日誌
[GC [ParNew: 310K->205K(2368K), 0.0006664 secs] 310K->205K(7680K), 0.0007043 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs] [GC [ParNew: 2253K->31K(2368K), 0.0032525 secs] 2253K->2295K(7680K), 0.0032911 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [Tenured: 2264K->194K(5312K), 0.0054415 secs] 4343K->194K(7680K),
[Perm : 2950K->2950K(21248K)], 0.0055105 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap par new generation total 2432K, used 43K [0x0000000005550000, 0x00000000057f0000, 0x0000000007150000) eden space 2176K, 2% used [0x0000000005550000, 0x000000000555aeb8, 0x0000000005770000) from space 256K, 0% used [0x0000000005770000, 0x0000000005770000, 0x00000000057b0000) to space 256K, 0% used [0x00000000057b0000, 0x00000000057b0000, 0x00000000057f0000) tenured generation total 5312K, used 194K [0x0000000007150000, 0x0000000007680000, 0x000000000a950000) the space 5312K, 3% used [0x0000000007150000, 0x0000000007180940, 0x0000000007180a00, 0x0000000007680000) compacting perm gen total 21248K, used 2982K [0x000000000a950000, 0x000000000be10000, 0x000000000fd50000) the space 21248K, 14% used [0x000000000a950000, 0x000000000ac39980, 0x000000000ac39a00, 0x000000000be10000) No shared spaces configured.
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseParallelGC」,使用Parallel+Serial Old組合進行垃圾回收的日誌
[GC [PSYoungGen: 4417K->288K(18688K)] 4417K->288K(61440K), 0.0007910 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [PSYoungGen: 288K->0K(18688K)] [PSOldGen: 0K->194K(42752K)] 288K->194K(61440K)
[PSPermGen: 2941K->2941K(21248K)], 0.0032663 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] Heap PSYoungGen total 18688K, used 321K [0x0000000034190000, 0x0000000035660000, 0x0000000048f90000) eden space 16064K, 2% used [0x0000000034190000,0x00000000341e05c0,0x0000000035140000) from space 2624K, 0% used [0x0000000035140000,0x0000000035140000,0x00000000353d0000) to space 2624K, 0% used [0x00000000353d0000,0x00000000353d0000,0x0000000035660000) PSOldGen total 42752K, used 194K [0x000000000a590000, 0x000000000cf50000, 0x0000000034190000) object space 42752K, 0% used [0x000000000a590000,0x000000000a5c0810,0x000000000cf50000) PSPermGen total 21248K, used 2982K [0x0000000005190000, 0x0000000006650000, 0x000000000a590000) object space 21248K, 14% used [0x0000000005190000,0x0000000005479980,0x0000000006650000)
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC」,使用ParNew+CMS+Serial Old組合進行垃圾回收的日誌
[Full GC (System) [CMS: 0K->194K(62656K), 0.0080796 secs] 4436K->194K(81792K),
[CMS Perm : 2941K->2940K(21248K)], 0.0081589 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs] Heap par new generation total 19136K, used 340K [0x0000000005540000, 0x0000000006a00000, 0x0000000006a00000) eden space 17024K, 2% used [0x0000000005540000, 0x0000000005595290, 0x00000000065e0000) from space 2112K, 0% used [0x00000000065e0000, 0x00000000065e0000, 0x00000000067f0000) to space 2112K, 0% used [0x00000000067f0000, 0x00000000067f0000, 0x0000000006a00000) concurrent mark-sweep generation total 62656K, used 194K [0x0000000006a00000, 0x000000000a730000, 0x000000000a940000) concurrent-mark-sweep perm gen total 21248K, used 2981K [0x000000000a940000, 0x000000000be00000, 0x000000000fd40000)
這四段GC日誌中提煉出一些共性:
一、日誌的開頭「GC」、「Full GC」表示此次垃圾收集的停頓類型,而不是用來區分新生代GC仍是老年代GC的。若是有Full,則說明本次GC中止了其餘全部工做線程。看到Full GC的寫法是「Full GC(System)」,這說明是調用System.gc()方法所觸發的GC。
二、「GC」中接下來的「DefNew」、「ParNew」、「PSYoungGen」、「CMS」表示的是新生代垃圾收集器的名稱,「PSYoungGen」中的「PS」指的是「Parallel Scavenge」,它是Parallel收集器的全稱。
三、以第一個爲例,方括號內部的「320K->194K(2368K)」、「2242K->0K(2368K)」,指的是該區域已使用的容量->GC後該內存區域已使用的容量(該內存區總容量)。方括號外面的「310K->194K(7680K)」、「2242K->2241K(7680K)」則指的是GC前Java堆已使用的容量->GC後Java堆已使用的容量(Java堆總容量)。
四、還以第一個爲例,再日後「0.0269163 secs」表示該內存區域GC所佔用的時間,單位是秒。最後的「[Times: user=0.00 sys=0.00 real=0.03 secs]」則更具體了,user表示用戶態消耗的CPU時間、內核態消耗的CPU時間、操做從開始到結束通過的鐘牆時間。後面兩個的區別是,鍾牆時間包 括各類非運算的等待消耗,好比等待磁盤I/O、等待線程阻塞,而CPU時間不包括這些耗時,但當系統有多CPU或者多核的話,多線程操做會疊加這些CPU 時間因此若是user或sys超過real是徹底正常的。
五、「Heap」後面就列舉出堆內存目前各個年代的區域的內存狀況
觸發GC的時機
最後總結一下何時會觸發一次GC,我的經驗看,有三種場景會觸發GC:
一、第一種場景應該很明顯,當年輕代或者老年代滿了,Java虛擬機沒法再爲新的對象分配內存空間了,那麼Java虛擬機就會觸發一次GC去回收掉那些已經不會再被使用到的對象
二、手動調用System.gc()方法,一般這樣會觸發一次的Full GC以及至少一次的Minor GC
三、程序運行的時候有一條低優先級的GC線程,它是一條守護線程,當這條線程處於運行狀態的時候,天然就觸發了一次GC了。這點也很好證實,不過要用到WeakReference的知識,後面寫WeakReference的時候會專門講到這個。