[TOC]java
本篇文章是本人閱讀《深刻理解JVM》和《java虛擬機規範》時的筆記。
記錄的都是一些概念性的東西。
JVM是HotSpot,jdk1.7。
大神繞路,不喜勿噴。程序員
先來蜻蜓點水般地瀏覽一些著名的GC算法。
這裏也僅僅是說一下大體過程,具體細節的介紹對於我一個Java程序員來講表示無能爲力,由於底層實現要牽扯到具體的實現語言了,並且不一樣的JVM實現商確定有不一樣的實現細節。算法
這種算法的大概過程是:多線程
標記出全部須要回收的對象併發
統一回收全部被標記的對象佈局
這種算法很直觀,但他的缺點以下:性能
標記和清除的兩個階段,效率並非很好,由於回收的粒度太細了spa
清除後的內存區域通常都是千瘡百孔,可用內存區域通常都不連續線程
上面說的標記/清除算法不太好的主要緣由就是其回收粒度
太過細微了。
籤於此,複製算法的主要作法是:日誌
將內存分爲大小相等的兩塊,暫且稱之爲內存塊
每次當某一內存塊(A)佔滿以後,將該內存塊(A)的有用數據複製到另一塊內存塊(B)
將A內存塊的整個塊直接清除
這種算法相比於標記/清除算法的最大特色是:
每次回收都是之內存塊爲單位(粒度較大)
只有兩個內存塊,收集完後內存不連續的狀況也就不用考慮了
可是,將整個內存分爲兩塊,實際的可用內存也就減半了(這就有點沒法接受了)
存在大量的對象複製操做
上面說的複製算法的最大缺點就是對象的複製操做。尤爲是在有效的對象不少的狀況下。
這裏的標記/整理算法的大體過程是:
標記應該回收的區域
將有用的對象/數據集中移動到一塊區域,暫且稱之爲"有效區",有效區的位置每每是在兩端的某一端
將"有效區"以外的區域集體清理
既然上面說集中算法都各有優劣,那麼根據他們各自的優勢,在不一樣的狀況下使用最優的算法會不會更好呢?
分代收集的大體思路就是這樣的:
將JVM堆內存分爲新生代和年老代
年老代中的對象存活率通常都很高,採用'標記/清理'或'標記/複製'算法
新生代中通常對象的'死亡率'都很高,採用複製算法
上面說了一大堆GC的理論。可是忽略了一點:
怎麼肯定哪些對象或內存區域是能夠被回收的呢???
在java中對於對象是否還「活着」,採用的不是像Python或者其餘語言中的"引用計數"的方法。
java中採用的是"可達性分析"。
至於可達性分析的細節不必去深究,可是由"判斷對象是否還存活?"引出的另外一個問題卻不得不考慮,看下文。
不管採用什麼方法去區分哪些對象還活着,不得不作的一個讓步就是:這個斷定過程當中必須暫時讓其餘全部的線程都暫時停頓,這個現象對於JVM中的各個對象來講就至關於整個世界中止了。也就是所謂的Stop The World
。
這個停頓固然是有必要的,好比你開始分析對象的存活狀態時一個對象是無用的,當你分析完成後那個對象卻讓其餘線程操做了變成有效對象了。
因此,在整個判斷過程當中,要可以確保一致性。也就免不了Stop The World
。
固然,應用的規模越大,Stop The World
帶來的影響越大。
因此,頻繁的GC也不見得是好事。
上面說的都是GC的大體理論知識,如今看看GC的實現:垃圾收集器。
Serial收集器是衆多垃圾收集器中的元老。是一個單線程的收集器。在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束(Stop The World)。
雖然它的出現很是早,可是它依然是虛擬機運行在Client模式下的默認新生代收集器
,也有其獨特的優勢:
簡單而高效(與其餘收集器的單線程比)
對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷
這個ParNew的介紹是來自《深刻理解JVM》的做者說的,與本人沒任何關係 ^_^ .. ^_^
ParNew收集器其實就是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集以外,其他行爲包括Serial收集器可用的全部控制參數(例如:-XX:SurvivorRatio、 -XX:PretenureSizeThreshold、 -XX:HandlePromotionFailure等)、 收集算法、 Stop The World、 對象分配規則、 回收策略等都與Serial收集器徹底同樣,在實現上,這兩種收集器也共用了至關多的代碼。
是許多運行在Server模式下的虛擬機中首選的新生代收集器,其中有一個與性能無關但很重要的緣由是,除了Serial收集器外,目前只有它能與CMS收集器配合工做
ParNew收集器在單CPU的環境中絕對不會有比Serial收集器更好的效果
甚至因爲存在線程交互的開銷,該收集器在經過超線程技術實現的兩個CPU的環境中都不能百分之百地保證能夠超越Serial收集器。
固然,隨着可使用的CPU的數量的增長,它對於GC時系統資源的有效利用仍是頗有好處的。 它默認開啓的收集線程數與CPU的數量相同,在CPU很是多的環境下,可使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數
他的特色以下:
是一個新生代收集器
使用複製算法的收集器
並行的多線程收集器
Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量
吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
也常常稱爲「吞吐量優先」收集器
-XX:MaxGCPauseMillis ==> 控制最大垃圾收集停頓時間,大於零的毫秒數
-XX:GCTimeRatio ==> 直接設置吞吐量大小,吞吐量的倒數,既然是個比率,也就是個0到100的整數
-XX:+UseAdaptiveSizePolicy
是一個開關參數,當這個參數打開以後,就不須要手工指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、 晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了.
虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomics)
Serial收集器的老年代版本
一個單線程收集器
使用「標記-整理」算法
是Parallel Scavenge收集器的老年代版本
使用多線程和「標記-整理」算法
CMS:Concurrent Mark Sweep
是一種以獲取最短回收停頓時間爲目標的收集器
此處的停頓指的就是上文提到的"Stop The World"
其名稱中的MS指的就是Mark Sweep,它採用的算法就是標記/清除
他有另一個名字就是:Concurrent Low Pause Collector(併發、低停頓)
他的缺點以下:
對CPU資源很是敏感
它雖然不會致使用戶線程停頓,可是會由於佔用了一部分線程而致使應用程序變慢,總吞吐量會下降
沒法處理浮動垃圾
標記/清除------->內存不連續
是一款面向服務端應用的垃圾收集器
並行與併發
G1能充分利用多CPU、 多核環境下的硬件優點,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時間,部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓Java程序繼續執行
分代收集
空間整合
不會產生內存空間碎片,收集後能提供規整的可用內存
分配大對象時不會由於沒法找到連續內存空間而提早觸發下次GC
可預測的停頓:低停頓
《深刻理解JVM》一書是這麼說的:
在G1以前的其餘收集器進行收集的範圍都是整個新生代或者老年代,而G1再也不是這樣。 使用G1收集器時,Java堆的內存佈局就與其餘收集器有很大差異,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,它們都是一部分Region(不須要連續)的集合。
GC日誌的格式乍看起來亂七八糟,烏漆嘛黑的。固然他確定是有格式的。就拿《深刻理解JVM》中的這段代碼來講吧:
public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 這個成員屬性的惟一意義就是佔點內存,以便能在GC日誌中看清楚是否被回收過 */ byte[] bigSize = new byte[2 * _1MB]; public static void main(String[] args) { ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; // 假設在這行發生GC,objA和objB是否能被回收? System.gc(); } }
虛擬機參數:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
在個人機器(jdk1.7)上輸出以下:
2016-12-17T16:11:19.650+0800: 0.093: [GC [PSYoungGen: 5427K->568K(38400K)] 5427K->568K(124416K), 0.0016819 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2016-12-17T16:11:19.652+0800: 0.095: [Full GC [PSYoungGen: 568K->0K(38400K)] [ParOldGen: 0K->463K(86016K)] 568K->463K(124416K) [PSPermGen: 2514K->2513K(21504K)], 0.0109008 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap PSYoungGen total 38400K, used 998K [0x00000007d5c80000, 0x00000007d8700000, 0x0000000800000000) eden space 33280K, 3% used [0x00000007d5c80000,0x00000007d5d79a60,0x00000007d7d00000) from space 5120K, 0% used [0x00000007d7d00000,0x00000007d7d00000,0x00000007d8200000) to space 5120K, 0% used [0x00000007d8200000,0x00000007d8200000,0x00000007d8700000) ParOldGen total 86016K, used 463K [0x0000000781600000, 0x0000000786a00000, 0x00000007d5c80000) object space 86016K, 0% used [0x0000000781600000,0x0000000781673eb0,0x0000000786a00000) PSPermGen total 21504K, used 2520K [0x000000077c400000, 0x000000077d900000, 0x0000000781600000) object space 21504K, 11% used [0x000000077c400000,0x000000077c676178,0x000000077d900000)
解釋以下:
2016-12-17T16:11:19.650+0800 -XX:+PrintGCDateStamps的做用,就是GC的時間了 0.093:表示的從JVM啓動以來通過的秒數 GC [PSYoungGen:.... GC發生的區域 PSYoungGen表示採用的收集器爲Parallel Scavenge 若是使用的是Serial收集器,新生代名爲「Default New Generation」,顯示就是「[DefNew」 若是使用的是ParNew收集器,新生代名稱爲「[ParNew」,意爲「Parallel New Generation」 若是採用的是Parallel Scavenge收集器,新生代名稱就是「PSYoungGen」 「Full」,說明此次GC是發生了Stop-The-World
GC日誌,暫時就先寫這麼多吧,在後續的文章中再詳細介紹GC日誌。
注:如下參數總結來自《深刻理解JVM》一書
UseSerialGC : 是否使用Serial收集器
啓用後將使用Serial + Serial Old
的組合來進行垃圾回收
這也是Client模式下的默認值
UseParNewGC : 是否使用ParNew收集器
將使用ParNew + Serial Old
的組合來進行垃圾回收
UseConcMarkSweepGC
啓用後將使用ParNew + CMS + Serial Old
的組合來進行垃圾回收
Serial Old 做爲CMS的後備收集器(Concurrent Mode Failure)
UseParallelGC
使用Parallel Scavenge + Serial Old
的組合來進行垃圾回收
這也是Server模式下的默認值
UseParallelOldGC
使用 Parallel Scavenge + Parallel Old
的組合來進行垃圾回收
SurvivorRatio
新生代中Eden和Survivor的比值
默認爲8,即:Eden:Survivor=8:1
PretenureSizeThreshold
這個大小值,表示對象大小大於多少以後直接分配到老年代而不進入新生代
MaxTenuringThreshold
這個年齡值表示對象在通過多少次Minor GC以後就進入老年代
每次Minor GC以後,對象的該屬性值就加1
UseAdaptiveSizePolicy
動態調節堆中各個區域的大小和進入老年代的年齡
HandlePromotionFailure
是否容許分配擔保失敗
擔保失敗指的是: 老年代的剩餘空間大小沒法容納新生代中的Eden和Survivor的狀況
ParallelGCThreads
並行GC的線程數
GCTimeRatio
GC時間佔總時間的比例
只有在使用Parallel Scavenge的狀況下生效
默認值:99
MaxGCPauseMillis
GC的最大停頓時間
只有在使用Parallel Scavenge的狀況下生效
CMSInitiatingOccupancyFraction
CMS收集器在老年代空間被佔用多少後觸發GC
只對CMS收集器生效
默認值:68%
UseCMSCompactAtFullCollection
CMS收集器在完成垃圾回收後是否進行內存碎片整理
只對CMS收集器生效
CMSFullGCsBeforeCompaction
CMS通過多少次GC後再進行碎片整理
也就是設置CMS收集器在進行N次垃圾收集後再進行一次碎片整理
只對CMS收集器生效
Minor GC指的是新生代的GC
Minor GC比較頻繁
速度也比較快
Full GC/Major GC指的是老年代的GC
Full GC的速度通常比Minor GC慢10倍左右
Full GC的出現每每會有Minor GC的伴隨
《深刻理解JVM》
《Java虛擬機規範》-JDK1.7