垃圾回收機(jī)制
比較好的文章:
1??可回收對(duì)象算法
- 目前查看對(duì)象是否需要回收的算法主要由兩種:
引用計(jì)數(shù)法和可達(dá)性分析; - 引用計(jì)數(shù)雖好,但是無(wú)法解決對(duì)象之間相互循環(huán)引用但是實(shí)際上這些對(duì)象沒(méi)什么卵用時(shí)造成的內(nèi)存泄漏;
java采用的是可達(dá)性分析算法(Reachability Analysis)來(lái)判斷對(duì)象是否存活:從GC Roots出發(fā),可以到達(dá)的對(duì)象就是存活的,到達(dá)不了的就要被GC;
GC Roots可以是虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象、方法區(qū)中類靜態(tài)屬性引用的對(duì)象、方法區(qū)中產(chǎn)量引用的對(duì)象、本地方法棧引用的對(duì)象;這些對(duì)象的特點(diǎn)就是不被GC管理(GC回收的是堆上對(duì)象,GC roots位于方法區(qū)、虛擬機(jī)棧和本地方法棧),可以作為GC Roots的對(duì)象見(jiàn)
可以作為GC Roots的對(duì)象;一個(gè)對(duì)象真正的死亡需要兩次被標(biāo)記的過(guò)程:如果對(duì)象在進(jìn)行可達(dá)性分析后沒(méi)有發(fā)現(xiàn)和GC Roots相連的引用鏈,將會(huì)被
第一次標(biāo)記并進(jìn)行一次篩選,篩選條件是此對(duì)象是否需要執(zhí)行finalize()方法,當(dāng)對(duì)象沒(méi)有重寫覆蓋finalize()方法或者finalize()方法已經(jīng)執(zhí)行過(guò)一次,則虛擬機(jī)認(rèn)為不需要執(zhí)行finalize();
如果虛擬機(jī)認(rèn)為需要執(zhí)行finalize(),則會(huì)將對(duì)象放入F-Queue隊(duì)列中,等待Finalizer線程執(zhí)行觸發(fā)finalize()方法。在這個(gè)隊(duì)列等待過(guò)程中,如果重新和引用鏈上的對(duì)象發(fā)生關(guān)聯(lián)(重新引用),第二次標(biāo)記的時(shí)候?qū)?huì)移除“等待回收”的對(duì)象集合,這個(gè)對(duì)象就重新存活了。若沒(méi)有,將會(huì)被第二次標(biāo)記然后被回收。引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference),這四種強(qiáng)度逐漸減弱。
四種引用介紹:
- 強(qiáng)應(yīng)用:就是我們代碼中最常見(jiàn)的引用方式,GC在強(qiáng)引用對(duì)象可達(dá)時(shí)不會(huì)回收;
- 軟引用:使用
java.lang.ref.SoftReference來(lái)創(chuàng)建軟引用,軟引用的特點(diǎn)是當(dāng)內(nèi)存不足時(shí),GC會(huì)將其回收;適合作為緩存使用 - 弱引用:使用
java.lang.ref.WeakReference來(lái)創(chuàng)建弱引用,特點(diǎn)是每次GC都會(huì)將其回收,無(wú)論內(nèi)存充足與否; - 虛引用:使用
java.lang.ref.PhantomReference來(lái)創(chuàng)建虛引用,其特點(diǎn)是和引用它的對(duì)象生命周期無(wú)關(guān),GC任何情況下都會(huì)回收它。虛引用僅用來(lái)處理資源的清理問(wèn)題,比Object里面的finalize機(jī)制更靈活。get方法返回的永遠(yuǎn)是null,Java虛擬機(jī)不負(fù)責(zé)清理虛引用,但是它會(huì)把虛引用放到引用隊(duì)列里面。
使用:對(duì)于軟引用和弱引用,可以有兩種構(gòu)造方式:使用ReferenceQueue引用隊(duì)列或者不使用ReferenceQueue,例如:
WeakReference<String> wr = new WeakReference<String>(str);
或者
ReferenceQueue<String> rq = new ReferenceQueue<String>();
WeakReference<String> wr = new WeakReference<String>(str, rq);
軟引用和弱引用很相似,也是最常用的非強(qiáng)引用,
而對(duì)于虛引用PhantomReference只能配合ReferenceQueue使用:
ReferenceQueue<String> rq = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(str, rq);
2??垃圾收集算法
- 當(dāng)對(duì)象判斷為可回收后,需要專門的算法去執(zhí)行回收動(dòng)作。垃圾收集算法主要分為以下幾種:
- 標(biāo)記-清除算法,Mark-Sweep:顧名思義,缺點(diǎn)是效率慢,容易造成內(nèi)存碎片;
- 復(fù)制算法,Coping:將內(nèi)存空間分為幾塊區(qū)域,每次只使用其中一塊E,當(dāng)一塊快用完了,就將存活的對(duì)象復(fù)制到另外一塊S上,然后把使用的E一次性清除干凈。實(shí)際上這種算法用于新生代的垃圾回收,E即Eden,S就是Survivor,缺點(diǎn)是Survivor空間不夠時(shí)需要老年代內(nèi)存來(lái)?yè)?dān)保,即所謂的分配擔(dān)保(Handle Promoting);
- 標(biāo)記整理算法,Mark-Compact:和標(biāo)記-清除算法的區(qū)別在于,標(biāo)記后并不直接進(jìn)行清理,而是將存活對(duì)象向內(nèi)存空間的一端移動(dòng),然后直接清除存活邊界以外的內(nèi)存空間,避免碎片化;
- 分代收集算法,Generational Collection:實(shí)際上就是上述垃圾收集算法的組合,即在不同的內(nèi)存區(qū)域采用不同的收集算法。比如在新生代采用復(fù)制算法,在老年代采用標(biāo)記-整理/清理算法。
- 在Hotspot中使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的,在類加載完畢的時(shí)候,HotSpot就會(huì)把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來(lái)。在JIT中也會(huì)在特定的位置記錄下棧和寄存器的引用的位置。
- HotSpot并非為每條指令都生成OopMap,只在安全點(diǎn)SafePoint記錄。即程序執(zhí)行到安全點(diǎn)才暫停下來(lái)開(kāi)始GC。
- 收集器的性能指標(biāo):
1??吞吐率(1 - GC總時(shí)間/總時(shí)間):高吞吐量能提高CPU效率,適合交互較少的程序;
2??最大暫停時(shí)間:越短越適合交互比較多的程序;
3??堆空間使用率: - 常見(jiàn)的收集器:
- Serial:?jiǎn)尉€程收集器,新生代運(yùn)行,使用復(fù)制算法,在安全點(diǎn)暫停所有線程開(kāi)始GC;
- ParNew:多線程版本的Serial,新生代運(yùn)行;優(yōu)先配合CMS
- Parallel Scavenge:新生代收集器,使用復(fù)制算法,可以控制吞吐量,吞吐量?jī)?yōu)先收集器;配合Serial Old使用,不能搭配CMS;
- Serial Old:Serial的老年代版本,老年代運(yùn)行,使用標(biāo)記-整理算法;
- Parallel Old:Parallel Scavenge的老年代版本,優(yōu)先考慮配合Parallel Scavenge;
- CMS:Concurrent Mark Sweep,獲取最短暫停時(shí)間,老年代運(yùn)行;
- G1:Garbage First,最新的收集器,老年代和新生代都可以使用。
| GC | 描述 | 適用年代 | VM參數(shù) | 偏重 | 搭配 |
|---|---|---|---|---|---|
| Serial | 單線程收集器 | 新生代 | -XX:+UseSerialGC | 單核CPU | VM參數(shù)下同時(shí)啟動(dòng)Serial Old |
| ParNew | 多線程收集器 | 新生代 | -XX:+UseConcMarkSweepGC或-XX:+UseParNewGC | 多核CPU | CMS |
| Parallel Scavenge | 并行多線程 | 新生代 | -XX:+UseParallelGC | 吞吐量 | VM參數(shù)下默認(rèn)Serial Old |
| Serial Old | Serial的老年代版本 | 老年代 | -XX:+UseSerialGC | CMS的替補(bǔ) | Parallel Scavenge(jdk1.5以前) |
| Parallel Old | Parallel Scavenge的老年代版本 | 老年代 | -XX:-UseParallelOldGC | 吞吐量/CPU資源 | Parallel Scavenge |
| CMS | Concurrent Mark Sweep | 老年代 | -XX:+UseConcMarkSweepGC | 暫停時(shí)間 | -XX:+UseConcMarkSweepGC同時(shí)啟動(dòng)ParNew |
| G1 | 優(yōu)先搜集占用空間大的垃圾 | 全局 | -XX:+UseG1GC | 在暫停時(shí)間內(nèi)盡可能收集多內(nèi)存 | 全局使用 |
jdk 7默認(rèn)的GC回收器組合是ParNew + CMS;
一個(gè)典型的32位JVM,Java堆大小設(shè)置在2 GB(使用分代&并發(fā)收集器)通常為500 MB YoungGen分配空間和1.5 GB的OldGen空間。