本篇是JVM的第一篇學(xué)習(xí)筆記摹芙,主要學(xué)習(xí)書籍是《深入理解Java虛擬機(jī) 之JVM高級特性與最佳實踐》驮吱,總結(jié)歸納自己的學(xué)習(xí)心得阳距。
目錄
JVM學(xué)習(xí) 之 垃圾收集器
1免绿、 Java 內(nèi)存區(qū)域
1.1凡伊、程序計數(shù)器
1.2零渐、虛擬機(jī)棧
1.3、本地方法棧
1.4系忙、Java堆
1.5诵盼、方法區(qū)
1.6、運行時常量池
1.7银还、直接內(nèi)存
2风宁、垃圾回收器
2.1、對象
2.1.1蛹疯、可達(dá)性分析算法
2.1.2戒财、引用類型
2.1.3、方法區(qū)回收
2.2捺弦、垃圾收集算法
2.2.1饮寞、標(biāo)記-清除算法
2.2.2、復(fù)制算法
2.2.3列吼、標(biāo)記-整理算法
2.2.4幽崩、分代收集算法
2.3、垃圾收集器
2.3.1寞钥、Serial 收集器
2.3.2慌申、ParNew 收集器
2.3.3、Parallel Scavenge 收集器
2.3.4理郑、Serial Old 收集器
2.3.5蹄溉、Parallel Old 收集器
2.3.6、CMS 收集器
2.3.7您炉、G1 收集器
2.4柒爵、常用參數(shù)
3、后續(xù)
1赚爵、 Java 內(nèi)存區(qū)域
在C餐弱、C++開發(fā)中宴霸,由開發(fā)者自行管理所有對象的內(nèi)存申請與釋放,但是Java有自己的內(nèi)存管理機(jī)制膏蚓,在絕大部分場景下可以自動完成內(nèi)存的申請和內(nèi)存釋放,不太容易出現(xiàn)內(nèi)存泄露和內(nèi)存溢出的問題
- 內(nèi)存溢出Out Of Memory(OOM):直接導(dǎo)致的問題就是內(nèi)存不夠用畸写,例如需要申請10M的內(nèi)存驮瞧,現(xiàn)在只有5M的可用內(nèi)存,就會提示OOM錯誤枯芬,也就是內(nèi)存溢出
- 內(nèi)存泄露Memory Leak:申請的內(nèi)存空間沒有很好的釋放回收论笔,經(jīng)過多次內(nèi)存泄露操作,就可能轉(zhuǎn)變?yōu)镺OM
在Java程序執(zhí)行過程中千所,虛擬機(jī)會把管理的內(nèi)存根據(jù)不同的需求劃分為若干不同的區(qū)域狂魔,如下圖是《Java 虛擬機(jī)規(guī)范(Java SE 7版)》規(guī)定所包含的各種數(shù)據(jù)區(qū)域
1.1、程序計數(shù)器
程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間淫痰,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器最楷。字節(jié)碼解釋器就是通過修改該數(shù)據(jù)選取下一條需要執(zhí)行的指令,包含循環(huán)待错、跳轉(zhuǎn)籽孙、分支、異常處理等情況火俄。
每一個線程獨有單獨的程序計數(shù)器犯建,每一個處理器在特定的時刻也只會運行一個線程。
如果執(zhí)行的是Java方法瓜客,記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址适瓦。如果是native方法,計數(shù)器為空(Undefined)谱仪。此內(nèi)存區(qū)域是唯一一個在java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域玻熙。
1.2、虛擬機(jī)棧
虛擬機(jī)棧(Java Virtual Machine Stacks)同樣是線程私有芽卿,和線程有著同樣的生命周期揭芍。
每一個方法在執(zhí)行時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧卸例、方法接口等信息称杨,每一個方法從開始調(diào)用到完成的過程就是一個棧幀在虛擬機(jī)棧中入棧和出棧的過程。其中局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型筷转。局部變量表所需的空間在編譯期完成分配姑原,一個32位類型的數(shù)據(jù)占用一個局部變量空間(Slot)。
- StackOverflowError:棧溢出錯誤呜舒,線程請求的棧深度超過了虛擬機(jī)所允許的深度锭汛,一般很難出現(xiàn)棧溢出錯誤,除非在代碼中出現(xiàn)了循環(huán)申請棧的情況。
- OutOfMemoryError:上面已經(jīng)說了內(nèi)存溢出是因為內(nèi)存不夠用導(dǎo)致的
如何編寫棧溢出錯誤的代碼呢唤殴?很簡單般婆,我們上面已經(jīng)說了每一個方法被調(diào)用就是入棧的操作,那么循環(huán)調(diào)用一個方法朵逝,不讓其出棧蔚袍,就可以出現(xiàn)棧溢出的錯誤了。具體如下圖配名,棧深度達(dá)到了近1w8
1.3啤咽、本地方法棧
本地方法棧(Native Method Stack)和虛擬機(jī)棧類似,但是本地方法棧則是服務(wù)于虛擬機(jī)使用到的native方法渠脉,例如一般的lib方法等宇整,和虛擬機(jī)棧類似,同樣會出現(xiàn)棧溢出和內(nèi)存溢出的情況芋膘。
1.4鳞青、Java堆
Java堆是所有線程共享的內(nèi)存區(qū)域,幾乎所有的對象實例都是在這里分配內(nèi)存索赏。Java堆是垃圾收集器管理的主要區(qū)域盼玄,也被成為GC堆(Garbage Collected Heap),細(xì)分區(qū)域可以分為新生代和老年代潜腻;再細(xì)致一點則有Eden空間(伊甸區(qū))埃儿,F(xiàn)orm Survivor、To Survivor等空間融涣。
從內(nèi)存分配的角度出發(fā)童番,線程共享的Java堆可能劃分出多個線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB)
1.5威鹿、方法區(qū)
方法區(qū)(Method Area)和Java堆一樣剃斧,是由各個線程所共享的區(qū)域,被用來存儲已經(jīng)被虛擬機(jī)加載的類信息忽你、常量幼东、靜態(tài)變量、即時編譯的數(shù)據(jù)等信息科雳。
在HotSpot虛擬機(jī)上根蟹,更多人原因把方法去稱為永久代(Permanent Generation),當(dāng)內(nèi)存不足時糟秘,則拋出OOM錯誤
1.6简逮、運行時常量池
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分,用來存放各種生成的字面量尿赚,例如定義的一個String類型的數(shù)據(jù)散庶,最后就被存在此處蕉堰,同樣的也會出現(xiàn)OOM錯誤
1.7、直接內(nèi)存
直接內(nèi)存(Direct Memory),不是虛擬機(jī)運行時區(qū)域內(nèi)的數(shù)據(jù)悲龟,可以利用native方法屋讶,直接分配堆外內(nèi)存,然后可以通過DirectoryByetBuffer對象對該數(shù)據(jù)進(jìn)行讀寫操作躲舌,在NIO中利用該種方法可以極大的提高數(shù)據(jù)傳輸丑婿,大小不受虛擬機(jī)控制,但是會由機(jī)器的內(nèi)存大小没卸、處理器等因素印象。
2秒旋、垃圾回收器
了解和學(xué)習(xí)垃圾收集器约计,了解GC過程、內(nèi)存分配便于我們實際解決各種內(nèi)存溢出迁筛、內(nèi)存泄露的問題煤蚌,雖然絕大部分問題都已經(jīng)由自動化的內(nèi)存動態(tài)分配和內(nèi)存回收機(jī)制解決掉了。
GC過程需要考慮的三個問題
- 哪些內(nèi)存需要回收
- 什么時候回收
- 如何回收
哪些內(nèi)存需要回收细卧,肯定是已經(jīng)消亡的尉桩,生命周期結(jié)束了,已經(jīng)不需要使用的對象贪庙。那么怎么判斷對象是否真的是不需要使用的呢蜘犁?
2.1、對象
2.1.1止邮、可達(dá)性分析算法
在堆中存放了JVM中幾乎所有的對象實例这橙,如何確認(rèn)哪些對象是存活的還是消亡的,很多語言都是使用了引用計數(shù)器导披,當(dāng)一個對象被引用一次就使得其對象的引用計數(shù)器+1屈扎,直到引用計數(shù)器為0,則認(rèn)為當(dāng)前對象沒有被引用撩匕,可以被回收掉鹰晨。
之前我也是這樣認(rèn)為的,python也是類似的解決方案止毕。但是java卻沒有使用該方法去管理內(nèi)存模蜡,主要原因是其很難解決對象之間的相互引用的問題,例如ObjectA 引用了ObjectB滓技,同時ObjectB也引用了ObjectA哩牍,那么就出現(xiàn)了相互持有的階段,如果是引用計數(shù)器方法令漂,意味著這兩個對象永遠(yuǎn)不能被回收
可達(dá)性分析算法
在Java中使用的是叫可達(dá)性分析算法膝昆,這個算法是通過一系列成為GC Roots的對象作為起點丸边,開始進(jìn)行搜索操作,搜索的路徑成為引用鏈荚孵,當(dāng)一個對象到GC Roots沒有任何引用鏈妹窖,也就是不可達(dá),則證明此對象可以被回收收叶。如下圖骄呼,對象C和對象D是獨立存在的,將會在搜索完成之后被判定為可回收對象
在Java中判没,可以作為GC Roots的對象包括為如下幾種
- 虛擬機(jī)棧中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法中JNI引用的對象
2.1.2蜓萄、引用類型
在我們一般的理解中引用就是一個reference類型的數(shù)據(jù)所存儲的值是另一塊內(nèi)存的起始地址,例如Object obj = new Object()
這句代碼澄峰,obj就是一個引用對象嫉沽,指向著new Object()
對象。在JDK1.2之后俏竞,Java對引用的概念進(jìn)行了擴(kuò)充绸硕,講引用分為了強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)魂毁、弱引用(Weak Reference)玻佩、虛引用(Phantom Reference),這四種引用強(qiáng)度依次逐漸減落席楚。
- 強(qiáng)引用:上面說的obj就是強(qiáng)引用咬崔,只要強(qiáng)已用一直在,垃圾收集器永遠(yuǎn)不會回收被引用的對象
- 軟引用:用來描述一些有用但不是必須的對象酣胀,在OOM之前刁赦,會將該類型對象放進(jìn)回收范圍內(nèi)進(jìn)行二次回收操作,如果這次回收之后還是沒有足夠的內(nèi)存闻镶,才會發(fā)生OOM甚脉,現(xiàn)提供了SoftReference類提供使用
- 弱引用:比軟引用的強(qiáng)度更低,被關(guān)聯(lián)的對象在下一次垃圾回收的時候就會被回收掉铆农,提供了WeakReference類實現(xiàn)弱引用
- 虛引用:也稱為幽靈引用或者幻影引用牺氨,一個對象是否存在虛引用的存在完全不會對其生存時間產(chǎn)生影響,也無法通過虛引用來取得一個對象實例墩剖。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能再這個對象被收集器回收的時候會收到一個系統(tǒng)通知猴凹,提供了PhantomReference類實現(xiàn)虛引用
2.1.3、方法區(qū)回收
在Java規(guī)范中確實說了不要氣虛擬機(jī)在方法區(qū)實現(xiàn)垃圾回收岭皂,而且在方法區(qū)進(jìn)行垃圾回收的性價比比較低郊霎。一般情況下,常規(guī)應(yīng)用進(jìn)行一次垃圾收集操作就會回收絕大部分的空間爷绘,在方法區(qū)進(jìn)行垃圾收集的效率確實低于此书劝。
在永久代中垃圾收集主要是回收兩部分內(nèi)容:廢棄變量和無用的類进倍,那么如何判斷一個類是否是無用的呢?
- 該類所有的實例已經(jīng)被回收购对,在JVM中不存在該類的任何實例
- 加載該類的ClassLoader也已經(jīng)被回收了
- 該類對應(yīng)的java.lang.Class 對象沒有在任何地方被引用猾昆,無法在任何地方通過反射訪問該類方法
2.2、垃圾收集算法
2.2.1骡苞、標(biāo)記-清除算法
Mark-Sweep算法垂蜗,包含了標(biāo)記和清除兩個階段
- 通過GC Roots方法,區(qū)分存活解幽、將要被回收的對象贴见,然后標(biāo)記將要被回收的對象
- 統(tǒng)一回收所有被標(biāo)記的對象
最基礎(chǔ)的清除算法,后續(xù)的算法都是對其進(jìn)行優(yōu)化的結(jié)果
主要的不足是
- 效率問題:標(biāo)記和清除操作的效率不高躲株,需要暫停用戶線程蝇刀,而且需要遞歸遍歷全堆的數(shù)據(jù)
- 空間問題:完成標(biāo)記清除后,會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片徘溢,如果后續(xù)需要插入連續(xù)的大塊內(nèi)存,沒有足夠的連續(xù)空間捆探,則會引發(fā)新的垃圾手機(jī)操作
2.2.2然爆、復(fù)制算法
復(fù)制算法的操作原理是標(biāo)記出存活的對象,然后把其存活的對象拷貝至其他內(nèi)存空間黍图,然后對之前的整個內(nèi)存空間進(jìn)行回收曾雕,而且在內(nèi)存分配的時候,不用考慮內(nèi)存碎片的情況助被,移動指針剖张,按順序分配內(nèi)存,不過這種算法對空間的使用率會偏低揩环,他必須在空間使用到一定比率的時候就開始進(jìn)行垃圾收集操作
在HotSpot虛擬機(jī)中搔弄,將內(nèi)存塊分配為一塊較大的Eden空間,還有兩塊較小的Survivor空間丰滑,每次都是使用Eden和其中的一塊Survivor空間顾犹,最后把存活的對象拷貝至另一塊未使用的Survivor空間,而對Eden和使用中的Survivor進(jìn)行垃圾收集褒墨。默認(rèn)情況Eden和Survivor的大小比率是8:1炫刷,這就意味著真正使用的內(nèi)存空間是90%,剩下的10%就是被浪費掉的(處于不被使用的情況)
那么這就存在一個問題了郁妈,如果存活的對象空間超過了10%浑玛,那么超出的對象講通過分配擔(dān)保機(jī)制存放到老年代(也就是上面說的方法區(qū))
該方法適用于朝生夕死的服務(wù),對象的存活率較低的情況下噩咪,使得復(fù)制的工作最少顾彰,效率達(dá)到最高
2.2.3极阅、標(biāo)記-整理算法
Mark-Compact算法,標(biāo)記過程和標(biāo)記清除算法相同拘央,但是后續(xù)步驟是把所有存活的對象移動到另一端涂屁,然后直接清除邊界外的內(nèi)存。
2.2.4灰伟、分代收集算法
現(xiàn)在的商業(yè)虛擬機(jī)的垃圾收集器都是采用分代收集(Generational Collection)算法拆又,一般情況下把堆分為新生代和老年代,根據(jù)不同的年代特點使用最合適的收集算法栏账。如果每次垃圾收集后的對象存活率很低帖族,那使用復(fù)制算法效率最高,而老年代的對象存活率高挡爵,而且沒有額外的空間對其進(jìn)行分配擔(dān)保竖般,就必須使用標(biāo)記-清除或者標(biāo)記-整理算法進(jìn)行回收操作。
2.3茶鹃、垃圾收集器
垃圾收集器是垃圾回收算法的具體實現(xiàn)涣雕,一般情況下不同的廠商有著不同的垃圾收集器,主要學(xué)習(xí)的是HotSpot虛擬機(jī)的收集器關(guān)聯(lián)關(guān)系闭翩,具體如下圖挣郭。一個新生代的收集器和一個老年代的收集器相結(jié)合,用直線連接的收集器才能夠配套使用疗韵,例如Serial不能和Parallel Old收集器一起使用兑障。
不過有一個觀點是必須接受的,目前不存在最好的收集器蕉汪,更沒有萬能的收集器流译,只有在特定場景下的相對合適的收集器,需要結(jié)合具體應(yīng)用場景。
接下來就具體學(xué)習(xí)上面的幾種收集器,學(xué)習(xí)他們的使用原理吱肌,優(yōu)點和缺點,適用在什么樣的場景下
2.3.1竞漾、Serial 收集器
早起最基礎(chǔ)的新生代垃圾收集器,采用的是復(fù)制算法窥翩,是單一線程的收集器业岁,在進(jìn)行垃圾回收時,必須暫停其他所有工作線程的運行寇蚊,直到收集結(jié)束笔时。線程暫停也就是俗稱的“Stop The World”,早起他是和老年代的Serial Old 一起工作完成收集的任務(wù)仗岸。如下圖是Serial & Serial Old 收集器運行示意圖
虛擬機(jī)在Client模式下默認(rèn)的新生代收集器允耿。對于限定單個CPU的環(huán)境來說借笙,簡單高效,Serial收集器由于沒有線程交互的開銷较锡,專心做垃圾收集自然可以獲得最高的單線程收集效率业稼。所有Serial收集器對于運行在Client模式下的虛擬機(jī)是一個很好的選擇
2.3.2、ParNew 收集器
Serial 收集器的多線程版本蚂蕴,同樣是復(fù)制算法低散,在進(jìn)行收集操作時,可以啟用多個線程同時進(jìn)行回收操作骡楼。
ParNew收集器在單CPU環(huán)境下絕對不會有比Serial收集器更好的效果熔号,由于存在線程之間的同步切換等開銷,該收集器在兩個CPU環(huán)境中也未必就可以使得效率超過Serial收集器鸟整,可以使用-XX:parallelGCThreads參數(shù)來限制收集器線程數(shù)
2.3.3引镊、Parallel Scavenge 收集器
同樣是新生代的收集器,也是采用的復(fù)制算法篮条,但是和ParNew收集器還是存在諸多不同的地方
Parallel Scavenge收集器主要的特點是達(dá)到一個可控制的吞吐量弟头,吞吐量就是CPU用于運行用戶代碼的時間和CPU總耗時間的對比值,如果在100分鐘內(nèi)涉茧,垃圾回收花費了1分鐘亮瓷,則吞吐量就是99%,停頓時間越短越適合于需要與用戶交互的程序降瞳,高吞吐量則可以充分的使用CPU時間,適合用于在后臺運算而不需要太多的交互任務(wù)蚓胸。
Parallel Scavenge收集器提供了兩個參數(shù)來精確控制吞吐量:
- -XX:MaxGCPauseMillis 最大垃圾收集器停頓時間(大于0的毫秒數(shù)挣饥,停頓時間小了就要犧牲相應(yīng)的吞吐量和新生代空間)
- -XX:GCTimeRatio 吞吐量大小(大于0小于100的整數(shù)沛膳,默認(rèn)99扔枫,也就是允許最大1%的垃圾回收時間)
因為和吞吐量有著密切的關(guān)系,也被稱為吞吐量優(yōu)先收集器锹安,除了上述兩個參數(shù)外短荐,還有一個開關(guān)參數(shù)-XX:+UseAdaptiveSizePolicy
,當(dāng)這個參數(shù)打開后,不需要手工指定新生代的大小-Xmn
和Eden與Survivor的比例-XX:SurvivorRatio
叹哭、晉升老年代對象大小-XX:PretenureSizeThreshold
等細(xì)節(jié)參數(shù)忍宋,虛擬機(jī)會根據(jù)當(dāng)前的吞吐量自行適配以達(dá)到最合適的停頓時間或者最大的吞吐量
自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge 收集器和ParNew 收集器的區(qū)別之一
2.3.4、Serial Old 收集器
Serial收集器對應(yīng)的老年代收集器风罩,同樣是單線程工作糠排,使用標(biāo)記-整理算法,具體如下圖所示
2.3.5超升、Parallel Old 收集器
是Parallel Savenge 收集器的老年代版本實現(xiàn)入宦,使用多線程和整理-標(biāo)記算法哺徊,從JDK1.6才開始提供的。
2.3.6乾闰、CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一種獲取最短回收停頓時間為目標(biāo)的收集器落追,主要應(yīng)用在服務(wù)端,加快響應(yīng)速度涯肩,基于標(biāo)記-清除算法轿钠,他的運行主要是包含了4個步驟
- 初始標(biāo)記(CMS initial mark)
- 并發(fā)標(biāo)記(CMS concurrent mark)
- 重新標(biāo)記(CMS remark)
- 并發(fā)清除(CMS concurrent sweep)
其中初始標(biāo)記、重新標(biāo)記都是需要“Stop The World”宽菜,也就是暫停用戶線程的執(zhí)行谣膳。
- 初始標(biāo)記僅僅是標(biāo)記通過GC Roots能直接關(guān)聯(lián)的對象,速度很快
- 并發(fā)標(biāo)記就是進(jìn)行GC Roots Tracing的過程铅乡,標(biāo)記出所有的對象继谚,會判斷對象是否存活,可以和用戶線程同步進(jìn)行
- 重新標(biāo)記則是為了修正在并發(fā)標(biāo)記階段因用戶線程執(zhí)行導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象標(biāo)記記錄
- 并發(fā)清除是耗時比較重要的操作阵幸,完成上述標(biāo)記的需要被清除的對象的清除操作
CMS收集器的優(yōu)點:并發(fā)收集和低停頓花履,但是也有幾個缺點
- 對CPU資源非常敏感:默認(rèn)的回收線程個數(shù)是(CPU數(shù)量+3) / 4,垃圾回收器始終不會占據(jù)低于25%的CPU資源挚赊,當(dāng)CPU個數(shù)不足四個的時候诡壁,例如兩個還需要分配近50%的CPU能力去執(zhí)行收集器線程,這會極大的消耗CPU的有效資源
- 無法處理浮動的垃圾:因為在并發(fā)清理階段不會暫停用戶線程荠割,這其中就會產(chǎn)生新的額外的未回收垃圾妹卿,只能等到下一次GC時再清理,同時由于CMS收集器在垃圾收集階段蔑鹦,用戶線程還在運行夺克,那還需要預(yù)留足夠的內(nèi)存空間提供給用戶線程使用,因此CMS收集器不和其他收集器一般嚎朽,等到?jīng)]有內(nèi)存了再進(jìn)行收集操作铺纽,需要預(yù)留一部分空間提供給并發(fā)收集過程中的用戶線程使用,在JDK1.5中哟忍,CMS收集器在老年代空間使用到了68%時狡门,就會被激活,可以通過設(shè)置參數(shù)
-XX:CMSInitiatingOccupancyFraction
提供觸發(fā)百分比锅很,在JDK1.6中其馏,該值默認(rèn)修改為92%了,如果再CMS運行期間預(yù)留的內(nèi)存無法滿足用戶線程的執(zhí)行爆安,會出現(xiàn)Concurrent Mode Failure錯誤尝偎,將會導(dǎo)致啟動Full GC 操作。實際生產(chǎn)環(huán)境中需要注意該參數(shù),如果該參數(shù)過大很容易導(dǎo)致大量的Concurrent Mode Failure失敗致扯,反而會影響性能 - 標(biāo)記清除算法本身的缺點:上文可知標(biāo)記清除算法會導(dǎo)致大量的空間碎片肤寝,一旦沒有足夠的連續(xù)空間分配給大對象,則會提前出發(fā)Full GC 操作抖僵,為了解決該問題鲤看,CMS收集器提供了一個開關(guān)參數(shù)
-XX:UseCMSCompactAtFullCollection
,用戶在CMS進(jìn)行Full GC操作時進(jìn)行的內(nèi)存碎片合并整理的過程耍群,這個階段不是并發(fā)的义桂,會使得停頓時間加長,還提供了另一個參數(shù)-XX:CMSFullGCBeforeCompaction
蹈垢,用來設(shè)置執(zhí)行了多少次不壓縮的Full GC后緊接著來一次帶壓縮的Full GC 操作慷吊,默認(rèn)為0,表示每次進(jìn)入Full GC操作時都進(jìn)行碎片整理曹抬。
2.3.7溉瓶、G1 收集器
G1(Garbage First)收集器是當(dāng)前最完善的收集器,在JDK1.7中可用谤民,未來可用替換掉CMS收集器堰酿,其特點包含了
- 并行與并發(fā):充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢张足,使用多個CPU和縮短Stop The World停頓的時間
- 分代收集:分代概念依舊保留触创,能夠采用不同方式去處理新建對象和已經(jīng)存活了一段時間、熬過多次GC的老年代對象以獲取更好收集效果
- 空間整合:整體是基于標(biāo)記-整理的算法为牍,但是局部來看則是采用了復(fù)制算法實現(xiàn)哼绑,這兩種算法都不會產(chǎn)生內(nèi)存空間碎片,收集后可提供規(guī)整的可用內(nèi)存碉咆,有利于程序長時間運行凌那,分配大對象不會因為沒有可用內(nèi)存而提前觸發(fā)GC操作
- 可預(yù)測的停頓:G1除了追求低停頓之外,還能建立可預(yù)測的停頓時間模型吟逝,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間卻不得超過N毫秒
特點說明:
- G1之前的收集器收集的范疇是整個新生代或者老年代赦肋,G1收集器則是把整個Java堆劃分為多個大小相等的獨立區(qū)域Region块攒,新生代和老年代不再是物理概念上的隔離,都是一部分Region的集合
- G1之所以可以建立可預(yù)測的停頓時間模型佃乘,是因為其可以有計劃的避免在整個的Java堆中進(jìn)行全區(qū)域的垃圾收集囱井,他會跟蹤各個Region里面的垃圾值大小,在后臺維護(hù)一個優(yōu)先隊列趣避,每次根據(jù)允許的時間庞呕,優(yōu)先回收垃圾值最大的Region,這樣可以有效的保證了G1收集器可以在有限的時間內(nèi)獲取盡可能高的收集效率
- G1對內(nèi)存化整為零的思路,雖然把內(nèi)存劃分為多個Region后住练,垃圾就完全按照Region為單位進(jìn)行垃圾回收地啰,Region本身是虛構(gòu)的概念,和實際的物理區(qū)域沒有太多的關(guān)系讲逛,必然存在某一個對象可能被多個毫無關(guān)聯(lián)的Region所引用亏吝,這樣就會導(dǎo)致對整個的Java堆的全局掃描,這明顯是不能被接受的盏混!為此在G1收集器中蔚鸥,Region之間的對象引用,以及其他收集器中新生代和老年代的對象引用许赃,虛擬機(jī)都是使用Remembered Set來避免全堆掃描止喷。G1中每個Region都有一個與之對應(yīng)的Remembered Set,虛擬機(jī)發(fā)現(xiàn)程序在對Reference類型的數(shù)據(jù)進(jìn)行寫操作時混聊,會產(chǎn)生一個Write Barrier暫時中斷寫操作弹谁,檢查Reference引用的對象是否處于不同的Region之中(分代的例子中就檢查是否老年代對象引用了新生代的對象),如果是則通過CardTable把相關(guān)引用信息記錄到被引用對象所屬的Region的Remembered Set之中技羔,當(dāng)進(jìn)行內(nèi)存回收時僵闯,在GC根節(jié)點的枚舉范圍中加入Remembered Set即可避免全堆掃描。
其主要運行的步驟分為
- 初始標(biāo)記(Initial Marking)
- 并發(fā)標(biāo)記(Concurrenr Marking)
- 最終標(biāo)記(Final Marking)
- 篩選回收(Live Data Counting And Evacution)
其中在最終標(biāo)記階段則是為了修正在并發(fā)標(biāo)記階段因用戶線程執(zhí)行導(dǎo)致標(biāo)記發(fā)生變化的那一部分的標(biāo)記情況藤滥,這段時間內(nèi)的變化將記錄在Remembered Set Logs中鳖粟,最終標(biāo)記的時候會把Remembered Set Logs的記錄合并到Remembered Set中,需要暫停用戶線程拙绊,但是可以并行執(zhí)行
最后的篩選回收階段向图,G1收集器會評估各個Region的垃圾值,根據(jù)用戶希望的GC停頓時間制定回收計劃标沪,因為停頓的只有一部分Region榄攀,所以可以大范圍的提高回收效率。
2.4金句、常用參數(shù)
這些常見的參數(shù)以及使用特定還是需要熟悉的
參數(shù) | 描述 |
---|---|
-XX:+UseSerialGC | Jvm運行在Client模式下的默認(rèn)值檩赢,打開此開關(guān)后,使用Serial + Serial Old的收集器組合進(jìn)行內(nèi)存回收 |
-XX:+UseParNewGC | 打開此開關(guān)后违寞,使用ParNew + Serial Old的收集器進(jìn)行垃圾回收 |
-XX:+UseConcMarkSweepGC | 使用ParNew + CMS + Serial Old的收集器組合進(jìn)行內(nèi)存回收贞瞒,Serial Old作為CMS出現(xiàn)“Concurrent Mode Failure”失敗后的后備收集器使用 |
-XX:+UseParallelGC | Jvm運行在Server模式下的默認(rèn)值,打開此開關(guān)后趁曼,使用Parallel Scavenge + Serial Old的收集器組合進(jìn)行回收 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge + Parallel Old的收集器組合進(jìn)行回收 |
-XX:SurvivorRatio | 新生代中Eden區(qū)域與Survivor區(qū)域的容量比值军浆,默認(rèn)為8,代表Eden:Subrvivor = 8:1 |
-XX:PretenureSizeThreshold | 直接晉升到老年代對象的大小挡闰,設(shè)置這個參數(shù)后乒融,大于這個參數(shù)的對象將直接在老年代分配 |
-XX:MaxTenuringThreshold | 晉升到老年代的對象年齡掰盘,每次Minor GC之后,年齡就加1赞季,當(dāng)超過這個參數(shù)的值時進(jìn)入老年代 |
-XX:UseAdaptiveSizePolicy | 動態(tài)調(diào)整java堆中各個區(qū)域的大小以及進(jìn)入老年代的年齡 |
-XX:+HandlePromotionFailure | 是否允許新生代收集擔(dān)保愧捕,進(jìn)行一次minor gc后, 另一塊Survivor空間不足時,將直接會在老年代中保留 |
-XX:ParallelGCThreads | 設(shè)置并行GC進(jìn)行內(nèi)存回收的線程數(shù) |
-XX:GCTimeRatio | GC時間占總時間的比列碟摆,默認(rèn)值為99晃财,即允許1%的GC時間,僅在使用Parallel Scavenge 收集器時有效 |
-XX:MaxGCPauseMillis | 設(shè)置GC的最大停頓時間典蜕,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction | 設(shè)置CMS收集器在老年代空間被使用多少后出發(fā)垃圾收集断盛,默認(rèn)值為68%,僅在CMS收集器時有效愉舔,-XX:CMSInitiatingOccupancyFraction=70 |
-XX:+UseCMSCompactAtFullCollection | 由于CMS收集器會產(chǎn)生碎片钢猛,此參數(shù)設(shè)置在垃圾收集器后是否需要一次內(nèi)存碎片整理過程,僅在CMS收集器時有效 |
-XX:+CMSFullGCBeforeCompaction | 設(shè)置CMS收集器在進(jìn)行若干次垃圾收集后再進(jìn)行一次內(nèi)存碎片整理過程轩缤,通常與UseCMSCompactAtFullCollection參數(shù)一起使用 |
-XX:+UseFastAccessorMethods | 原始類型優(yōu)化 |
-XX:+DisableExplicitGC | 是否關(guān)閉手動System.gc |
-XX:+CMSParallelRemarkEnabled | 降低標(biāo)記停頓 |
-XX:LargePageSizeInBytes | 內(nèi)存頁的大小不可設(shè)置過大命迈,會影響Perm的大小,-XX:LargePageSizeInBytes=128m |
3火的、后續(xù)
在17年新推出的Java9壶愤,就開始把G1收集器作為默認(rèn)的收集器,CMS不再被推薦使用
在Java 7和Java8中的默認(rèn)收集器是擁有高吞吐能力的Parallel GC收集器
G1是一個擁有可以預(yù)測停頓時間模型的這么一個收集器馏鹤,那么其優(yōu)點必然就是低延遲了