二 JAVA垃圾收集器
2.1 垃圾收集簡(jiǎn)史
垃圾收集提供了內(nèi)存管理的機(jī)制,使得應(yīng)用程序不需要在關(guān)注內(nèi)存如何釋放,內(nèi)存用完后,垃圾收集會(huì)進(jìn)行收集,這樣就減輕了因?yàn)槿藶榈墓芾韮?nèi)存而造成的 錯(cuò)誤,比如在C++語(yǔ)言里,出現(xiàn)內(nèi)存泄露時(shí)很常見(jiàn)的。Java語(yǔ)言是目前使用最多的依賴于垃圾收集器的語(yǔ)言,但是垃圾收集器策略從20世紀(jì)60年代就已經(jīng) 流行起來(lái)了,比如Smalltalk,Eiffel等編程語(yǔ)言也集成了垃圾收集器的機(jī)制。
2.2 常見(jiàn)的垃圾收集策略

所有的垃圾收集算法都面臨同一個(gè)問(wèn)題,那就是找出應(yīng)用程序不可到達(dá)的內(nèi)存塊,將其釋放,這里面得不可到達(dá)主要是指應(yīng)用程序已經(jīng)沒(méi)有內(nèi)存塊的引用了, 而在JAVA中,某個(gè)對(duì)象對(duì)應(yīng)用程序是可到達(dá)的是指:這個(gè)對(duì)象被根(根主要是指類的靜態(tài)變量,或者活躍在所有線程棧的對(duì)象的引用)引用或者對(duì)象被另一個(gè)可 到達(dá)的對(duì)象引用。
2.2.1 Reference Counting(引用計(jì)數(shù))
引用計(jì)數(shù)是最簡(jiǎn)單直接的一種方式,這種方式在每一個(gè)對(duì)象中增加一個(gè)引用的計(jì)數(shù),這個(gè)計(jì)數(shù)代表當(dāng)前程序有多少個(gè)引用引用了此對(duì)象,如果此對(duì)象的引用計(jì)數(shù)變?yōu)?,那么此對(duì)象就可以作為垃圾收集器的目標(biāo)對(duì)象來(lái)收集。
優(yōu)點(diǎn):
簡(jiǎn)單,直接,不需要暫停整個(gè)應(yīng)用
缺點(diǎn):
1.需要編譯器的配合,編譯器要生成特殊的指令來(lái)進(jìn)行引用計(jì)數(shù)的操作,比如每次將對(duì)象賦值給新的引用,或者者對(duì)象的引用超出了作用域等。
2.不能處理循環(huán)引用的問(wèn)題
2.2.2 跟蹤收集器
跟蹤收集器首先要暫停整個(gè)應(yīng)用程序,然后開(kāi)始從根對(duì)象掃描整個(gè)堆,判斷掃描的對(duì)象是否有對(duì)象引用,這里面有三個(gè)問(wèn)題需要搞清楚:

1.如果每次掃描整個(gè)堆,那么勢(shì)必讓GC的時(shí)間變長(zhǎng),從而影響了應(yīng)用本身的執(zhí)行。因此在JVM里面采用了分代收集,在新生代收集的時(shí)候minor gc只需要掃描新生代,而不需要掃描老生代。
2.JVM采用了分代收集以后,minor gc只掃描新生代,但是minor gc怎么判斷是否有老生代的對(duì)象引用了新生代的對(duì)象,JVM采用了卡片標(biāo)記的策略,卡片標(biāo)記將老生代分成了一塊一塊的,劃分以后的每一個(gè)塊就叫做一個(gè)卡 片,JVM采用卡表維護(hù)了每一個(gè)塊的狀態(tài),當(dāng)JAVA程序運(yùn)行的時(shí)候,如果發(fā)現(xiàn)老生代對(duì)象引用或者釋放了新生代對(duì)象的引用,那么就JVM就將卡表的狀態(tài)設(shè) 置為臟狀態(tài),這樣每次minor gc的時(shí)候就會(huì)只掃描被標(biāo)記為臟狀態(tài)的卡片,而不需要掃描整個(gè)堆。具體如下圖:
3.GC在收集一個(gè)對(duì)象的時(shí)候會(huì)判斷是否有引用指向?qū)ο螅贘AVA中的引用主要有四種:Strong reference,Soft reference,Weak reference,Phantom reference.
◆ Strong Reference
強(qiáng)引用是JAVA中默認(rèn)采用的一種方式,我們平時(shí)創(chuàng)建的引用都屬于強(qiáng)引用。如果一個(gè)對(duì)象沒(méi)有強(qiáng)引用,那么對(duì)象就會(huì)被回收。
- public void testStrongReference(){
- Object referent = new Object();
- Object strongReference = referent;
- referent = null;
- System.gc();
- assertNotNull(strongReference);
- }
◆ Soft Reference
軟引用的對(duì)象在GC的時(shí)候不會(huì)被回收,只有當(dāng)內(nèi)存不夠用的時(shí)候才會(huì)真正的回收,因此軟引用適合緩存的場(chǎng)合,這樣使得緩存中的對(duì)象可以盡量的再內(nèi)存中待長(zhǎng)久一點(diǎn)。
- Public void testSoftReference(){
- String str = "test";
- SoftReference<String> softreference = new SoftReference<String>(str);
- str=null;
- System.gc();
- assertNotNull(softreference.get());
- }
◆ Weak reference
弱引用有利于對(duì)象更快的被回收,假如一個(gè)對(duì)象沒(méi)有強(qiáng)引用只有弱引用,那么在GC后,這個(gè)對(duì)象肯定會(huì)被回收。
- Public void testWeakReference(){
- String str = "test";
- WeakReference<String> weakReference = new WeakReference<String>(str);
- str=null;
- System.gc();
- assertNull(weakReference.get());
- }
◆ Phantom reference
2.2.2.1 Mark-Sweep Collector(標(biāo)記-清除收集器)
標(biāo)記清除收集器最早由Lisp的發(fā)明人于1960年提出,標(biāo)記清除收集器停止所有的工作,從根掃描每個(gè)活躍的對(duì)象,然后標(biāo)記掃描過(guò)的對(duì)象,標(biāo)記完成以后,清除那些沒(méi)有被標(biāo)記的對(duì)象。
優(yōu)點(diǎn):
1 解決循環(huán)引用的問(wèn)題
2 不需要編譯器的配合,從而就不執(zhí)行額外的指令
缺點(diǎn):
1.每個(gè)活躍的對(duì)象都要進(jìn)行掃描,收集暫停的時(shí)間比較長(zhǎng)。
2.2.2.2 Copying Collector(復(fù)制收集器)復(fù)制收集器將內(nèi)存分為兩塊一樣大小空間,某一個(gè)時(shí)刻,只有一個(gè)空間處于活躍的狀態(tài),當(dāng)活躍的空間滿的時(shí)候,GC就會(huì)將活 躍的對(duì)象復(fù)制到未使用的空間中去,原來(lái)不活躍的空間就變?yōu)榱嘶钴S的空間。復(fù)制收集器具體過(guò)程可以參考下圖:

優(yōu)點(diǎn):
1 只掃描可以到達(dá)的對(duì)象,不需要掃描所有的對(duì)象,從而減少了應(yīng)用暫停的時(shí)間
缺點(diǎn):
1.需要額外的空間消耗,某一個(gè)時(shí)刻,總是有一塊內(nèi)存處于未使用狀態(tài)
2.復(fù)制對(duì)象需要一定的開(kāi)銷
2.2.2.3 Mark-Compact Collector(標(biāo)記-整理收集器)標(biāo)記整理收集器汲取了標(biāo)記清除和復(fù)制收集器的優(yōu)點(diǎn),它分兩個(gè)階段執(zhí)行,在第一個(gè)階段,首先掃描所有活躍的對(duì)象,并 標(biāo)記所有活躍的對(duì)象,第二個(gè)階段首先清除未標(biāo)記的對(duì)象,然后將活躍的的對(duì)象復(fù)制到堆得底部。標(biāo)記整理收集器的過(guò)程示意圖請(qǐng)參考下圖:Mark- compact策略極大的減少了內(nèi)存碎片,并且不需要像Copy Collector一樣需要兩倍的空間。