一 JVM內(nèi)存分配與回收
1.1 新創(chuàng)建對象優(yōu)先在eden區(qū)分配
一般來說趣惠,新創(chuàng)建的對象都會在堆內(nèi)存中eden區(qū)分配空間额衙。當(dāng)eden區(qū)沒有足夠空間的時候捆毫,jvm將進(jìn)行一次Minor GC栓撞,Minor GC與Full GC區(qū)別如下
- 新生代GC(Minor GC):指發(fā)生新生代的的垃圾收集動作辟宗,Minor GC非常頻繁舔琅,回收速度一般也比較快
- 老年代GC(Major GC/Full GC):指發(fā)生在老年代的GC等恐,出現(xiàn)了Full GC經(jīng)常會伴隨至少一次的Minor GC(并非絕對),F(xiàn)ull GC的速度一般會比Minor GC慢10倍以上
GC測試:
先添加jvm打印gc詳情參數(shù):-XX:+PrintGCDetails
public class Test3 {
public static void main(String[] args) {
byte[]
allocation1 = new byte[28000*1024]/*,
allocation2 = new byte[900*1024],
allocation3 = new byte[1000*1024],
allocation4 = new byte[1000*1024],
allocation5 = new byte[1000*1024],
allocation6 = new byte[1000*1024]*/;
}
}
1.1.1 先只創(chuàng)建allocation1對象
輸出結(jié)果如下:
可以看到eden內(nèi)存空間被占滿备蚓,我們初始化了28M空間课蔬,實際eden內(nèi)存空間使用了33M左右是因為,剩余的5M空間是JVM用于初始化自己的一些對象所占用的星著,即使我們不創(chuàng)建對象新生代也會使用至少5M的空間购笆,為了驗證我們剛剛的說法,我們將main方法中new操作代碼全部屏蔽虚循,結(jié)果輸出如下同欠,可以看出印證了我們的結(jié)論
1.1.2 創(chuàng)建allocation1對象和allocation2對象
詳細(xì)查看方法
取消new allocation2對象的代碼注釋,運行程序横缔,結(jié)果輸出如下
在Young Generation(新生代)的Eden區(qū)的空間不足以容納新生成的對象時執(zhí)行minorGC
可以看到铺遂,因為創(chuàng)建allocation2對象需要大概不到1M的空間,使得原本創(chuàng)建allocation1對象以后已經(jīng)100%的eden空間超過容量茎刚,然后開始進(jìn)行一次minor GC襟锐,而這次GC會將allocation1和allcation2理論上都挪移到survivor中的from去,因為這里兩個對象理論上都存活膛锭,可是from只有5M的空間粮坞,光一個allocation1就有28M,因此根據(jù)大對象直接移動到老年代原則(分配擔(dān)保機制)初狰,將allocation1對象直接移動到了老年代莫杈,我們在上面的紅框中可以看到,老年代使用的空間正好是allocation1對象的占用空間(多余的8K為零散數(shù)據(jù)奢入,無需理會)筝闹,老年代上的空間足夠存放allocation1,所以不會出現(xiàn)Full GC。而allcation2對象則進(jìn)入了from區(qū)
1.1.3 繼續(xù)加上創(chuàng)建allocation3對象
可以從下圖中看到关顷,新創(chuàng)建的allocation3對象在eden區(qū)重新分配
1.2 大對象直接進(jìn)入老年代
大對象就是需要大量連續(xù)內(nèi)存空間的對象糊秆,比如字符串,數(shù)組议双。原因是:
因為如果eden區(qū)滿了痘番,一個對象在survivor中需要經(jīng)歷15次minor gc,如果大對象不直接進(jìn)入老年代聋伦,大對象來來回回在from和to區(qū)復(fù)制的效率比較低
1.3 長期存活的對象進(jìn)入老年代
虛擬機采用了分代收集的思想來管理內(nèi)存夫偶,那么內(nèi)存回收時就必須能識別那些對象應(yīng)放在新生代界睁,那些對象應(yīng)放在老年代中觉增。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數(shù)器翻斟,這個對象年齡是Minor GC年齡逾礁,即經(jīng)歷過多少次Minor GC
如果對象在 Eden 出生,此時年齡為0歲访惜,并經(jīng)過第一次 Minor GC 后仍然能夠存活嘹履,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中债热,并將對象年齡設(shè)為1.對象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲砾嫉,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),還沒銷毀掉窒篱,則在下一次GC來臨的時候焕刮,就會被晉升到老年代中。對象晉升到老年代的年齡閾值墙杯,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)置【tenure 英[?tenj?(r)]】
二 如何判斷對象是否可以回收
堆中幾乎放著所有的對象實例配并,對堆垃圾回收前的第一步就是要判斷那些對象已經(jīng)死亡(即不能再通過任何途徑使用的對象)
2.1 引用計數(shù)法
給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它高镐,計數(shù)器就加1溉旋;當(dāng)引用失效,計數(shù)器就減1嫉髓;任何時候計數(shù)器為0的對象就是不可能再被使用的观腊。
這個方法實現(xiàn)簡單,效率高算行,但是目前主流的虛擬機中并沒有選擇這個算法來管理內(nèi)存梧油,其最主要的原因是它很難解決對象之間相互循環(huán)引用的問題。 所謂對象之間的相互引用問題纱意,如下面代碼所示:除了對象obj1 和 obj2 相互引用著對方之外婶溯,這兩個對象再無任何引用。但是他們因為互相引用對方,導(dǎo)致它們的引用計數(shù)器都不為0迄委,于是引用計數(shù)算法無法通知 GC 回收器回收他們褐筛,也就進(jìn)入一種死循環(huán)的狀態(tài),可能導(dǎo)致內(nèi)存泄露
public class MyObject {
public Object ref =null;
public static void main(String[] args) {
MyObject myObject1 =newMyObject();
MyObject myObject2 =newMyObject();
myObject1.ref = myObject2;
myObject2.ref = myObject1;
myObject1 =null;
myObject2 =null;
}
}
2.2 可達(dá)性分析算法
通過一系列稱為“GC Roots”的對象作為起點叙身,從這些節(jié)點開始向下檢索渔扎,沿著節(jié)點所走過的路線叫做引用鏈,當(dāng)一個對象和GC Roots之間沒有任何一條引用鏈相連信轿,即沒有路可以從GC Roots走到這個對象的時候晃痴,則證明該對象是不可用可以被回收的
常見的GC Roots根節(jié)點:
類加載器、Thread财忽、虛擬機棧中的本地變量表倘核、static成員、常量引用即彪、本地方法棧中的變量等等
2.3 finalize()方法最終決定對象存活與否
經(jīng)歷可達(dá)性分析算法以后的不可達(dá)對象(對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots連接的引用鏈路)并不是直接被回收的紧唱,要真正宣告一個對象死亡,還要經(jīng)歷標(biāo)記過程隶校,標(biāo)記需要經(jīng)歷兩次標(biāo)記
第1次標(biāo)記并進(jìn)行一次判斷篩選
判斷對象是否有必要執(zhí)行finalize()方法
當(dāng)對象沒有覆蓋父類Object的finalize方法漏益,或者finalize方法已經(jīng)被調(diào)用過(對象在方法中沒有進(jìn)行自救,仍然沒有引用鏈讓他指向GC Roots根)深胳,則jvm將這兩種情況都視為需要執(zhí)行回收绰疤,直接進(jìn)行GC,對象被回收
第2次標(biāo)記
如果對象在第一次判斷的時候判斷為有必要執(zhí)行finalize()方法舞终,即對象覆蓋了父類Object的finalize方法轻庆,并且finalize方法沒有被調(diào)用過
則該對象會被放在名為 F-Queue的隊列中等待被一條jvm自動創(chuàng)建的,低優(yōu)先級的Finalizer線程去執(zhí)行(因為可能帶來性能損耗)权埠,但并不承諾會等待他運行結(jié)束(因為如果對象的finalize方法執(zhí)行緩慢榨了,或死循環(huán),很可能導(dǎo)致F-Queue隊列中其他對象永久等待攘蔽,甚至內(nèi)存回收系統(tǒng)奔潰)
finalize方法是對象避免死亡的最后一次機會龙屉,如果對象想要在finalize()方法中自救,則只需要重新與GC Roots引用鏈撒花姑娘的任何一個對象建立關(guān)聯(lián)即可满俗,譬如把自己賦值給某個類變量或者對象的成員變量转捕,那么第二次標(biāo)記時它將會被移除即將回收的集合,如果這時候?qū)ο筮€沒·逃脫唆垃,則其將會被真正的回收
// 重寫R中的finalize方法
@Override
protected void finalize() throws Throwable {
System.out.println("對象即將被回收"+status);
}
// main方法
public class Test {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i=0,j =0;
for(;;){
list.add(new R(i++,UUID.randomUUID().toString(),null));
new R(j--, UUID.randomUUID().toString(),null);
}
}
}
注意:finalize()只會在對象內(nèi)存回收前被調(diào)用一次(The finalize method is never invoked more than once by a Java virtual machine for any given object. )五芝;
總結(jié):基于在自我救贖中的表現(xiàn)來看,此方法有很大的不確定性(不保證方法中的任務(wù)執(zhí)行完)而且運行代價較高辕万。所以用來回收資源也不會有什么好的表現(xiàn)枢步。因此盡量不要使用 finalize()
2.4 如何判斷一個常量是廢棄常量
如果在運行時常量池中存在字符串“abc”沉删,且當(dāng)前沒有任何string對象引用該字符串常量的話,則常量“abc”就屬于廢棄常量醉途,這時候如果發(fā)生內(nèi)存回收矾瑰,有必要的話(即不一定回收),則“abc”將會被清理出常量池
2.5 如何判斷一個類是無用的類
在方法區(qū)中回收的是無用的類隘擎,判斷一個類是否無用殴穴,需要同時滿足3個條件
(1)該類的所有實例均已經(jīng)被回收,即堆中無該類實例
(2)加載該類的類加載器classLoader已經(jīng)被回收
(3)該類對應(yīng)的java.lang.Class對象沒有在任何地方被引用货葬,無法在任何地方通過反射獲取該類的方法
滿足上面的條件以后采幌,jvm將可能對該無用類進(jìn)行回收,之所以說可能震桶,是不一定回收休傍,看心情,不像對象一樣不使用必然被回收
三 垃圾收集算法
3.1 標(biāo)記清除算法(mark-sweep)
特點:效率低尼夺,內(nèi)存碎片多
標(biāo)記清除顧名思義是一種分兩階段對對象進(jìn)行垃圾回收的算法尊残。
第一階段:標(biāo)記。從根結(jié)點出發(fā)遍歷對象(從第一個格子開始遍歷所有的格子)淤堵,對訪問過的對象打上標(biāo)記,表示該對象可達(dá)
第二階段:清除顷扩。對那些沒有標(biāo)記的對象進(jìn)行回收拐邪,這樣使得不能利用的空間能夠重新被利用
算法優(yōu)點:
- 實現(xiàn)簡單
- 不移動對象,與保守式GC算法兼容隘截。在保守式GC算法中對象是不能移動的扎阶。
算法的缺點:
- 內(nèi)存碎片化。清理后的可用內(nèi)存被存活對象分隔開婶芭,導(dǎo)致塊是不連續(xù)的东臀,容易出現(xiàn)空閑內(nèi)存很多,但分配大對象時找不到合適的塊犀农。比如我要用上三下三的6個格子分配一個對象惰赋,但是第沒有滿足條件的連續(xù)的區(qū)塊供我們使用,這樣會導(dǎo)致GC呵哨,然而其實空間是夠放的
- 分配速度慢赁濒。即使是First-fit(找到大于等于size的塊立即返回),但其操作仍是一個O(n)的操作孟害,最壞情況是每次都要遍歷到最后拒炎。同時因為碎片化,大對象的分配效率會更慢
3.2 復(fù)制算法(coping)
為了解決標(biāo)記清理算法內(nèi)存碎片化的缺陷而提出來挨务。按內(nèi)存容量將內(nèi)存劃分為等大的兩塊击你,每次只使用其中一塊玉组,當(dāng)這塊被使用的內(nèi)存滿后將里面尚存活的對象復(fù)制到另一塊上去,把已使用的內(nèi)存清理掉
算法優(yōu)點:
實現(xiàn)簡單丁侄、內(nèi)存效率高球切、不易產(chǎn)生碎片
算法缺點:
可用內(nèi)存被壓縮到原來的一半,且存活對象增多的話绒障,復(fù)制算法的效率會降低很多
3.3 標(biāo)記整理算法(mark-compact)
考慮到上面兩個算法的缺點吨凑,大佬們結(jié)合了上面兩個算法的優(yōu)點,提出了標(biāo)記整理算法
標(biāo)記整理算法的標(biāo)記階段和標(biāo)記清除算法相同户辱,區(qū)別在于標(biāo)記整理算法在標(biāo)記階段完成之后并不是直接清除可回收對象鸵钝,而是將存活對象轉(zhuǎn)移到內(nèi)存的一端,然后將這個存活對象端邊界外的對象全部清理庐镐,這樣兼顧了標(biāo)記清除(簡單恩商,用完整的內(nèi)存)和復(fù)制算法(不會產(chǎn)生內(nèi)存碎片化)的優(yōu)點
3.4 分代收集算法(generational collecting)
當(dāng)前虛擬機的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想必逆,只是根據(jù)對象存活周期的不同將內(nèi)存分為幾塊怠堪。一般將java堆分為新生代和老年代,這樣我們就可以根據(jù)各個年代的特點選擇合適的垃圾收集算法
比如在新生代中名眉,每次收集都會有大量對象死去粟矿,所以可以選擇復(fù)制算法,因為尚存活的對象少损拢,因此要復(fù)制的操作比較少陌粹,只需要付出少量對象的復(fù)制成本就可以完成每次垃圾收集
而老年代的對象存活幾率是比較高的,老年代每次都回收少量對象福压,因此采用標(biāo)記整理算法掏秩,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集
JVM每次只會使用eden和其中一塊survivor來為對象服務(wù),所以無論什么時候荆姆,都會有一塊survivor空間必盖,因此新生代實際可用空間只有90%
四 垃圾收集器
jdk11出現(xiàn)了ZGC收集器(可處理T級別的堆內(nèi)存GC)疏之,jdk9使用G1收集器
GC收集算法是內(nèi)存回收的方法論耕渴,則垃圾收集器就是內(nèi)存回收的具體執(zhí)行者酱酬,我們可以指定使用哪種垃圾收集器,垃圾收集器會使用某種算法或者某幾種算法組合完成GC任務(wù)
雖然我們對各個收集器進(jìn)行比較腐泻,但并非為了挑選出一個最好的收集器决乎。因為直到現(xiàn)在為止還沒有最好的垃圾收集器出現(xiàn),更加沒有萬能的垃圾收集器派桩,我們能做的就是根據(jù)具體應(yīng)用場景選擇適合自己的垃圾收集器构诚。試想一下:如果有一種四海之內(nèi)、任何場景下都適用的完美收集器存在铆惑,那么我們的HotSpot虛擬機就不會實現(xiàn)那么多不同的垃圾收集器了
4.1 Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Serial(串行)收集器收集器是最基本范嘱、歷史最悠久的垃圾收集器了送膳。這個收集器是一個單線程收集器了。它的 “單線程” 的意義不僅僅意味著它只會使用一條垃圾收集線程去完成垃圾收集工作丑蛤,更重要的是它在進(jìn)行垃圾收集工作的時候必須暫停其他所有的工作線程( "Stop The World" )叠聋,直到它收集結(jié)束
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法
虛擬機的設(shè)計者們當(dāng)然知道Stop The World帶來的不良用戶體驗受裹,所以在后續(xù)的垃圾收集器設(shè)計中停頓時間在不斷縮短(仍然還有停頓碌补,尋找最優(yōu)秀的垃圾收集器的過程仍然在繼續(xù))。
但是Serial收集器有沒有優(yōu)于其他垃圾收集器的地方呢棉饶?當(dāng)然有厦章,它簡單而高效(與其他收集器的單線程相比)。Serial收集器由于沒有線程交互的開銷(回收的時候沒有應(yīng)用程序搶CPU資源)照藻,自然可以獲得很高的單線程收集效率
4.2 ParNew收集器
是Serial收集器的多線程版本袜啃,除了使用多線程收集外,其余行為(控制參數(shù)幸缕、手機算法群发、回收策略等等)都和Serial收集器完全一樣
新生代采用復(fù)制算法,老年代采用標(biāo)記-整理算法
從上面的一個橫杠(一個線程)變成了多個橫杠(多個線程)
ParNew收集器是運行在Server模式下的首要選擇的垃圾收集器发乔,除了Serial以外熟妓,只有它能和CMS(真正意義上的并發(fā)收集器)收集器配合工作
并行和并發(fā)概念補充:
并行(Parallel) :指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)列疗。適合科學(xué)計算滑蚯、后臺處理等弱交互場景。 這個概念是針對垃圾收集線程來說的抵栈。我們提到的parNew,parallel都是并行收集器
并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行坤次,可能會交替執(zhí)行古劲,一個CPU上兩個都執(zhí)行就是并發(fā),兩個CPU一個跑用戶線程缰猴,一個CPU跑垃圾收集線程就是并行产艾,不是100%并行去執(zhí)行的),用戶程序在繼續(xù)運行滑绒,而垃圾收集器運行在另一個CPU上闷堡。適合Web應(yīng)用。CMS和G1收集器都是并發(fā)收集器
4.3 Parallel Scavenge收集器(-XX:+UseParallelGC(新生代)疑故,-XX:+UseParallelOldGC(老生代))
Parallel Scavenge 收集器類似于ParNew 收集器杠览,是jdk1.8 Server 模式(內(nèi)存大于2G,2個cpu)下的默認(rèn)收集器纵势,我們從下圖紅框可以驗證我們的說法
parallel收集器與parNew收集器的區(qū)別主要是:
Parallel Scavenge收集器關(guān)注點是吞吐量(高效率的利用CPU)踱阿。CMS等垃圾收集器的關(guān)注點更多的是用戶線程的停頓時間(提高用戶體驗)管钳。所謂吞吐量就是CPU中用于運行用戶代碼的時間與CPU總消耗時間的比值。 Parallel Scavenge收集器提供了很多參數(shù)供用戶找到最合適的停頓時間或最大吞吐量软舌,如果對于收集器運作不太了解的話才漆,可以選擇把內(nèi)存管理優(yōu)化交給虛擬機去完成也是一個不錯的選擇。
新生代采用復(fù)制算法佛点,老年代采用標(biāo)記-整理算法
收集過程基本和上面的parNew收集器一樣
4.4 Serial Old收集器
Serial收集器的老年代版本醇滥,它同樣是一個單線程收集器。它主要有兩大用途:一種用途是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用超营,另一種用途是作為CMS收集器的后備方案
4.5 Parallel Old收集器
Parallel Scavenge收集器的老年代版本鸳玩。使用多線程和“標(biāo)記-整理”算法。在注重吞吐量以及CPU資源的場合糟描,都可以優(yōu)先考慮 Parallel Scavenge收集器和Parallel Old收集器
4.6 CMS收集器(-XX:+UseConcMarkSweepGC(主要是old區(qū)使用))搭配XX:+UseParNewGC)
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器怀喉。它而非常符合在注重用戶體驗的應(yīng)用上使用,它是HotSpot虛擬機第一款真正意義上的并發(fā)收集器船响,它第一次實現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時工作
從名字中的Mark Sweep這兩個詞可以看出躬拢,CMS收集器是一種 “標(biāo)記-清除”算法實現(xiàn)的,它的運作過程相比于前面幾種垃圾收集器來說更加復(fù)雜一些见间。整個過程分為四個步驟:
(1)初始標(biāo)記:暫停所有的其他線程(STW)聊闯,并記錄下直接與GC Root相連的對象(即GC Roots樹的根節(jié)點的兒子,非孫子以及以后輩分)米诉,給他們打上標(biāo)記菱蔬,速度很快 ,用的時間極少甚至0.幾ms史侣,對程序幾乎沒有影響
(2)并發(fā)標(biāo)記: (耗時較長)
同時開啟GC和用戶線程拴泌,繼續(xù)從第一步的直接與GC Roots相連的對象開始向下標(biāo)記,可能這個期間用戶線程又創(chuàng)建了新的對象惊橱,這些新創(chuàng)建的對象也是與GC Roots根連接的蚪腐。
(3)重新標(biāo)記:
重新標(biāo)記階段就是為了修正并發(fā)標(biāo)記期間因為用戶程序繼續(xù)運行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段的時間稍長税朴,遠(yuǎn)遠(yuǎn)比并發(fā)標(biāo)記階段時間短
(4)并發(fā)清理: (耗時較長)
開啟用戶線程回季,同時GC線程開始對未標(biāo)記的區(qū)域做清掃
優(yōu)點:
在耗時較長的地方使用并發(fā),讓用戶線程工作正林。將一個比較長的GC過程拆成很多小段泡一,對每個小段做特殊處理,才能達(dá)到一次GC stw時間盡可能短觅廓,從而對應(yīng)用程序影響盡可能少
CMS是一款優(yōu)秀的垃圾收集器鼻忠,主要優(yōu)點:并發(fā)收集、低停頓哪亿。但是它有下面三個明顯的缺點:
對CPU資源敏感(會和用戶服務(wù)搶資源)粥烁;**
無法處理浮動垃圾(并發(fā)清理階段贤笆,在java業(yè)務(wù)程序線程與垃圾收集線程并發(fā)執(zhí)行過程中又產(chǎn)生的垃圾,這種浮動垃圾只能等到下一次gc再清理了)讨阻;**
它使用的回收算法-“標(biāo)記-清除”算法會導(dǎo)致收集結(jié)束時會有大量空間碎片產(chǎn)生
4.6 G1收集器(-XX:+UseG1GC)
jdk1.7出現(xiàn)芥永,jdk1.9默認(rèn)收集器,相比于CMS钝吮,G1收集器收集的能力更強埋涧,數(shù)倍于CMS收集器。G1 (Garbage-First)是一款面向服務(wù)器的垃圾收集器奇瘦,主要是針對配備了多顆處理器以及最大容量內(nèi)存的機器棘催。以提高滿足GC停頓時間要求的同時,還具備高吞吐量性能
G1將Java堆劃分為多個大小相等的獨立區(qū)域(Region)耳标,雖保留新生代和老年代的概念醇坝,但不再是物理隔閡了,它們都是(可以不連續(xù))Region的集合次坡。
分配大對象(直接進(jìn)Humongous區(qū)呼猪,專門存放短期巨型對象,不用直接進(jìn)老年代砸琅,避免Full GC的大量開銷)不會因為無法找到連續(xù)空間而提前觸發(fā)下一次GC宋距。因此G1適合少量大對象的情況
被視為JDK1.7中HotSpot虛擬機的一個重要進(jìn)化特征。它具備以下特點:
(1)并行與并發(fā):G1能充分利用CPU症脂、多核環(huán)境下的硬件優(yōu)勢谚赎,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間。部分其他收集器原本需要停頓Java線程來執(zhí)行GC動作诱篷,G1收集器仍然可以通過并發(fā)的方式讓java程序繼續(xù)執(zhí)行壶唤。
(2)分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念棕所。
(3)空間整合:與CMS的“標(biāo)記--清理”算法不同视粮,G1從整體來看是基于“標(biāo)記整理”算法實現(xiàn)的收集器;從局部上來看是基于“復(fù)制”算法實現(xiàn)的橙凳。
(4)可預(yù)測的停頓:這是G1相對于CMS的另一個大優(yōu)勢,降低停頓時間是G1 和 CMS 共同的關(guān)注點笑撞,但G1 除了追求低停頓外岛啸,還能建立可預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi)完成垃圾收集茴肥。
G1收集器的運作大致分為以下幾個步驟:
初始標(biāo)記(initial mark坚踩,STW):在此階段,G1 GC 對根進(jìn)行標(biāo)記瓤狐。該階段與常規(guī)的 (STW) 年輕代垃圾回收密切相關(guān)瞬铸。
并發(fā)標(biāo)記(Concurrent Marking):G1 GC 在整個堆中查找可訪問的(存活的)對象批幌。
最終標(biāo)記(Remark,STW):該階段是 STW 回收嗓节,幫助完成標(biāo)記周期荧缘。
篩選回收(Cleanup,STW):篩選回收階段首先對各個Region的回收價值和成本進(jìn)行排序拦宣,根據(jù)用戶所期望的GC停頓時間來制定回收計劃截粗,這個階段其實也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因為只回收一部分Region鸵隧,時間是用戶可控制的绸罗,而且停頓用戶線程將大幅提高收集效率。
可以看到和CMS收集器前3個步驟基本一致豆瘫,就在最后一個步驟不一致珊蟀,CMS是并發(fā)清理,但是G1是只允許GC線程工作外驱,因為它要做到一個指定時間結(jié)束GC的功能育灸,因此他為了更快的完成收集任務(wù),沒有允許用戶線程一起工作
G1收集器在后臺維護了一個優(yōu)先列表略步,每次根據(jù)允許的收集時間描扯,優(yōu)先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來)。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式趟薄,保證了GF收集器在有限時間內(nèi)可以盡可能高的收集效率
五 垃圾收集器的選擇
優(yōu)先調(diào)整堆的大小讓服務(wù)器自己來選擇
如果內(nèi)存小于100M绽诚,使用串行收集器
如果是單核,并且沒有停頓時間的要求杭煎,串行或JVM自己選擇
如果允許停頓時間超過1秒恩够,選擇并行或者JVM自己選
如果響應(yīng)時間最重要,并且不能超過1秒羡铲,使用并發(fā)收集器
下圖有連線的可以搭配使用蜂桶,官方推薦使用G1,因為性能高
六 調(diào)優(yōu)
6.1 調(diào)優(yōu)的目的
JVM調(diào)優(yōu)主要就是調(diào)整下面兩個指標(biāo)
停頓時間:垃圾收集器做垃圾回收中斷應(yīng)用執(zhí)行的時間也切。 -XX:MaxGCPauseMillis
吞吐量:用戶程序執(zhí)行的時間占總時間的比例扑媚。垃圾收集的時間和總時間的占比:1/(1+n),吞吐量為1-1/(1+n) 雷恃。 -XX:GCTimeRatio=n
6.2 調(diào)優(yōu)步驟
- 打印GC日志
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log
Tomcat則直接加在JAVA_OPTS變量里
分析日志得到關(guān)鍵性指標(biāo)
分析GC原因疆股,調(diào)優(yōu)JVM參數(shù)
很多時候項目啟動慢,都是大量類導(dǎo)致的元空間滿了倒槐,進(jìn)而導(dǎo)致系統(tǒng)Full gc增多旬痹,stw時間長。關(guān)于為何元空間是堆外內(nèi)存卻也會滿的原因參考1
關(guān)于為何元空間是堆外內(nèi)存卻也會滿的原因參考2
6.3 GC常用參數(shù)
堆棧設(shè)置
-Xss:每個線程的棧大小
-Xms:初始堆大小,默認(rèn)物理內(nèi)存的1/64
-Xmx:最大堆大小两残,默認(rèn)物理內(nèi)存的1/4
-Xmn:新生代大小
-XX:NewSize:設(shè)置新生代初始大小
-XX:NewRatio:默認(rèn)2表示新生代占年老代的1/2永毅,占整個堆內(nèi)存的1/3。
-XX:SurvivorRatio:默認(rèn)8表示一個survivor區(qū)占用1/8的Eden內(nèi)存人弓,即1/10的新生代內(nèi)存沼死。
-XX:MetaspaceSize:設(shè)置元空間大小
-XX:MaxMetaspaceSize:設(shè)置元空間最大允許大小,默認(rèn)不受限制票从,JVM Metaspace會進(jìn)行動態(tài)擴展漫雕。
垃圾回收統(tǒng)計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器
-XX:+UseParallelGC:設(shè)置并行收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParalledlOldGC:設(shè)置并行老年代收集器
-XX:+UseConcMarkSweepGC:設(shè)置CMS并發(fā)收集器
-XX:+UseG1GC:設(shè)置G1收集器
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)
并行收集器設(shè)置
-XX:ParallelGCThreads:設(shè)置并行收集器收集時使用的CPU數(shù)。并行收集線程數(shù)峰鄙。
-XX:MaxGCPauseMillis:設(shè)置并行收集最大暫停時間
-XX:GCTimeRatio:設(shè)置垃圾回收時間占程序運行時間的百分比浸间。公式為1/(1+n)
-XX:YoungGenerationSizeIncrement:年輕代gc后擴容比例,默認(rèn)是20(%)
CMS收集器設(shè)置
-XX:+UseConcMarkSweepGC:設(shè)置CMS并發(fā)收集器
-XX:+CMSIncrementalMode:設(shè)置為增量模式吟榴。適用于單CPU情況魁蒜。
-XX:ParallelGCThreads:設(shè)置并發(fā)收集器新生代收集方式為并行收集時,使用的CPU數(shù)吩翻。并行收集線程數(shù)兜看。
-XX:CMSFullGCsBeforeCompaction:設(shè)定進(jìn)行多少次CMS垃圾回收后,進(jìn)行一次內(nèi)存壓縮
-XX:+CMSClassUnloadingEnabled:允許對類元數(shù)據(jù)進(jìn)行回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到達(dá)閥值的時候狭瞎,才進(jìn)行CMS回收
-XX:+CMSIncrementalMode:設(shè)置為增量模式细移。適用于單CPU情況
-XX:ParallelCMSThreads:設(shè)定CMS的線程數(shù)量
-XX:CMSInitiatingOccupancyFraction:設(shè)置CMS收集器在老年代空間被使用多少后觸發(fā)
-XX:+UseCMSCompactAtFullCollection:設(shè)置CMS收集器在完成垃圾收集后是否要進(jìn)行一次內(nèi)存碎片的整理
G1收集器設(shè)置
-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的線程數(shù)量
-XX:G1HeapRegionSize:指定分區(qū)大小(1MB~32MB,且必須是2的冪)熊锭,默認(rèn)將整堆劃分為2048個分區(qū)
-XX:GCTimeRatio:吞吐量大小弧轧,0-100的整數(shù)(默認(rèn)9),值為n則系統(tǒng)將花費不超過1/(1+n)的時間用于垃圾收集
-XX:MaxGCPauseMillis:目標(biāo)暫停時間(默認(rèn)200ms)
-XX:G1NewSizePercent:新生代內(nèi)存初始空間(默認(rèn)整堆5%)
-XX:G1MaxNewSizePercent:新生代內(nèi)存最大空間
-XX:TargetSurvivorRatio:Survivor填充容量(默認(rèn)50%)
-XX:MaxTenuringThreshold:最大任期閾值(默認(rèn)15)
-XX:InitiatingHeapOccupancyPercen:老年代占用空間超過整堆比IHOP閾值(默認(rèn)45%),超過則執(zhí)行混合收集
-XX:G1HeapWastePercent:堆廢物百分比(默認(rèn)5%)
-XX:G1MixedGCCountTarget:參數(shù)混合周期的最大總次數(shù)(默認(rèn)8)