上篇文章中提到了jvm為一個(gè)新對(duì)象進(jìn)行內(nèi)存分配的過程和實(shí)現(xiàn),接下來我們看看如何將進(jìn)行垃圾回收。在jvm中,垃圾指的是已經(jīng)死亡的對(duì)象所占據(jù)的堆空間。這里便涉及如何確定一個(gè)對(duì)象已經(jīng)死亡?
引用計(jì)數(shù)法及可達(dá)性分析
引用計(jì)數(shù)法
每個(gè)對(duì)象被引用一次后,計(jì)數(shù)+1;如果引用者銷毀了,則引用計(jì)數(shù)-1;
優(yōu)點(diǎn):引用計(jì)數(shù)收集器可以很快的執(zhí)行,交織在程序運(yùn)行中。對(duì)程序需要不被長(zhǎng)時(shí)間打斷的實(shí)時(shí)環(huán)境比較有利。
弱點(diǎn):無法解決兩個(gè)類中的對(duì)象互相引用,計(jì)數(shù)無法為0的問題:
例如現(xiàn)在有兩個(gè)類:
Class A {
B aMember;
}
class B {
A bMember;
}
即使類A 和 類B 的對(duì)象都沒有被引用了,此時(shí)他們的引用計(jì)數(shù)無法為0。因?yàn)轭愔斜舜耸褂昧藢?duì)方的對(duì)象作為成員變量;
可達(dá)性分析法
把所有的引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn)GC ROOT開始,尋找對(duì)應(yīng)的引用節(jié)點(diǎn),找到這個(gè)節(jié)點(diǎn)以后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn),當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn),即無用的節(jié)點(diǎn),就是可以被回收的。
java中可作為GC Root的對(duì)象有
1.虛擬機(jī)棧中引用的對(duì)象(本地變量表)
2.方法區(qū)中靜態(tài)屬性引用的對(duì)象
3. 方法區(qū)中常量引用的對(duì)象
4.本地方法棧中引用的對(duì)象(Native對(duì)象)
當(dāng)標(biāo)記完所有存活的對(duì)象,我們就可以真的進(jìn)行回收了
回收方式
- 清除
將未被標(biāo)記存活的對(duì)象空間標(biāo)記為空閑,掛在free list上,等下次要分配空間時(shí),從free list中取出。這種的做法比較簡(jiǎn)單,但很容易產(chǎn)生內(nèi)存碎片,同時(shí)也容易出現(xiàn)總空間足夠,但沒有合適的內(nèi)存可分配的情況。
2.標(biāo)記整理算法
對(duì)標(biāo)記-清除算法進(jìn)行改進(jìn),在回收不存活的對(duì)象占用的空間后,會(huì)將所有的存活對(duì)象往左端空閑空間移動(dòng),并更新對(duì)應(yīng)的指針。它解決了內(nèi)存碎片的問題,但代價(jià)是壓縮算法消耗了更多的性能。
- 標(biāo)記復(fù)制算法
為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時(shí)把堆分成 一個(gè)對(duì)象 面和多個(gè)空閑面, 程序從對(duì)象面為對(duì)象分配空間,當(dāng)對(duì)象滿了,基于copying算法的垃圾 收集就從根集中掃描活動(dòng)對(duì)象,并將每個(gè) 活動(dòng)對(duì)象復(fù)制到空閑面,這樣空閑面變成了對(duì)象面,原來的對(duì)象面變成了空閑面,程序會(huì)在新的對(duì)象面中分配內(nèi)存。
這里KEN FOX對(duì)上述幾種算法實(shí)現(xiàn)了一個(gè)可視化演示,效果非常直觀:https://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/
總結(jié)
在文章開始,我們總結(jié)了
附錄
上述回收算法是根據(jù)我們所選用的垃圾回收器(Garbage Collector)來選用的,一般來說不同的代會(huì)采用不同的collector,如下圖:

針對(duì)新生代,它的垃圾回收器是Serial、ParNew和Parallel Scavenge,使用的是標(biāo)記復(fù)制算法。
Serial:適用于單線程運(yùn)行的環(huán)境,例如命令行程序。它會(huì)停止當(dāng)前應(yīng)用運(yùn)行,用單個(gè)線程去回收、壓縮內(nèi)存,在服務(wù)端程序上不適用。 可以在程序運(yùn)行時(shí)設(shè)置-XX:+UseSerialGC JVM 參數(shù)來使用 serial 的回收器.
Parallel: 現(xiàn)代jvm的默認(rèn)選項(xiàng)。當(dāng)CPU數(shù)大于2,就可用多線程進(jìn)行新生代的垃圾回收。進(jìn)行FullGC的時(shí)候還是會(huì)停止應(yīng)用的運(yùn)行。適用于需要完成大量任務(wù)并且能接受時(shí)間較長(zhǎng)的應(yīng)用暫停。
針對(duì)老年代,它的垃圾回收器是CMS 或 G1。
CMS (Concurrent Mark Sweep): 回收策略采用的是壓縮整理算法。適用于回收耗時(shí)要較短,例如桌面應(yīng)用程序,web服務(wù)器的請(qǐng)求、數(shù)據(jù)庫的查詢;用到的垃圾回收算法與 Parallel一樣. FullGC用多線程進(jìn)行處理,不過是在應(yīng)用運(yùn)行的時(shí)候,同時(shí)并發(fā)地運(yùn)行回收,最小化“停止程序運(yùn)行”的時(shí)間,但不進(jìn)行壓縮。
G1 (Garbage First): 對(duì)標(biāo)是CMS的替代品,可以進(jìn)行老年代的壓縮。但G1其實(shí)能在新生代和老年代中混用。
鏈接: https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html