提到JVM垃圾回收,總覺(jué)得離我們程序員有一定的距離。在JAVA中,那是系統(tǒng)自己干的事,我們關(guān)心那個(gè)干嘛?也就是說(shuō)我們?yōu)槭裁匆獙W(xué)習(xí)這個(gè)東西,大家開(kāi)開(kāi)心心地敲代碼不好嗎?
還真的不好,一方面我覺(jué)得我們可以學(xué)習(xí)下JAVA語(yǔ)言設(shè)計(jì)上的一些思想,另一方面,在我們以后從事一些較為高級(jí)一點(diǎn)的開(kāi)發(fā),尤其是性能調(diào)優(yōu)之類的,知道這些基礎(chǔ)知識(shí)就顯得很必要了。我打算從以下幾個(gè)方面開(kāi)始進(jìn)行簡(jiǎn)單地說(shuō)明。
GC如何知道哪些對(duì)象是垃圾對(duì)象?
GC不可能隨便指派說(shuō)哪個(gè)對(duì)象是垃圾,要有一定的依據(jù)。常用的標(biāo)記垃圾的算法有兩個(gè):
引用計(jì)數(shù)算法
引用計(jì)數(shù)算法,就是每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)器,當(dāng)該對(duì)象被引用的時(shí)候計(jì)數(shù)器加1,當(dāng)引用失效的時(shí)候,計(jì)數(shù)器減1。
那么這么做有什么缺點(diǎn)嗎?
那就是當(dāng)兩個(gè)對(duì)象相互引用的時(shí)候,這兩個(gè)對(duì)象都會(huì)無(wú)法釋放。
根搜索算法
從根對(duì)象開(kāi)始,所有能被觸及的對(duì)象都可以認(rèn)為是“存活的”對(duì)象,換句話說(shuō),就是“仍然使用的”對(duì)象。不能被觸及的對(duì)象,就會(huì)被認(rèn)為是垃圾,需要回收。

那么什么對(duì)象可作為GC Roots呢?
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象;
- 方法區(qū)中類靜態(tài)屬性的引用;
- 方法區(qū)中常量引用的對(duì)象;
- 本地方法棧中JNI(Native方法)引用的對(duì)象。
什么是虛擬機(jī)棧?
虛擬機(jī)棧為Java方法服務(wù)。說(shuō)的簡(jiǎn)單點(diǎn),就是我們平時(shí)所寫的Java方法,沒(méi)調(diào)用一個(gè)Java方法,就是入棧的過(guò)程,退出方法就是出棧的過(guò)程。每個(gè)Java方法對(duì)應(yīng)于虛擬機(jī)棧中的一個(gè)棧幀。
什么是本地方法棧?
本地方法棧為native方法服務(wù)。
方法區(qū)是什么呢?
方法區(qū)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。可以看做堆的一個(gè)邏輯部分。
常用的垃圾回收算法有哪些?
看下圖,整幢大樓燈火通明,其中不排除一些辦公室沒(méi)人但還是燈亮著的情況,為了節(jié)約資源,我們需要關(guān)掉那些辦公室沒(méi)人的燈。那么怎么辦呢?

標(biāo)記——清除算法
我從頂樓開(kāi)始到一樓,一塊一塊辦公區(qū)域看,對(duì)沒(méi)人的區(qū)域進(jìn)行關(guān)燈。將整個(gè)大樓看成內(nèi)存,燈亮著的區(qū)域表示有對(duì)象存在,燈滅著的表示空閑區(qū)域。我們一塊一塊區(qū)域檢查的這個(gè)過(guò)程就是標(biāo)記的過(guò)程,關(guān)燈的操作就是清除的過(guò)程。這就是標(biāo)記——清除算法。

弊端:
- 那就是費(fèi)時(shí)費(fèi)力,效率太低。
- 不連續(xù),不美觀(內(nèi)存碎片嚴(yán)重)。
復(fù)制回收算法
老板說(shuō)了,浪費(fèi)太嚴(yán)重了,到了晚上,我們對(duì)需要加班的同事進(jìn)行統(tǒng)一安排。假設(shè)大樓共10層,只能使用15層或者610層(畢竟晚上加班的人不多)。比如現(xiàn)在使用的就是15樓,到了晚上要用燈了,需要加班的同事自己去610樓找位置,保安一聽(tīng)樂(lè)了,再也不用一塊一塊區(qū)域關(guān)燈了,有需要的人都去610樓了,剩下的即便是燈亮著的辦公區(qū)域那也是沒(méi)人,讓我分別去15樓拉個(gè)總閘先(回收的過(guò)程)。

可以看到,在任意時(shí)刻只用到了內(nèi)存的一半。
弊端:
- 有需要的同事搬到6~10樓的過(guò)程,太麻煩。特別是需要加班的同事比較多的時(shí)候。(需要對(duì)有用的對(duì)象進(jìn)行復(fù)制)。
- 整棟大樓只能用一半,哎(內(nèi)存使用率降低)。
優(yōu)點(diǎn):
從外面看,我知道哪些地方有人,哪些地方?jīng)]人,方便了管理(內(nèi)存無(wú)碎片)。
標(biāo)記——整理算法
下班后,保安大哥將空的辦公區(qū)域依次統(tǒng)計(jì)出來(lái)(標(biāo)記的過(guò)程),需要加班的同事按照統(tǒng)計(jì)結(jié)果,依次搬到空閑的辦公區(qū)域。保安大哥知道,我只需要找到最后一個(gè)有人的區(qū)域,那么這塊區(qū)域之后肯定不會(huì)有人了,不用挨個(gè)檢查了,去拉后面的閘。

優(yōu)點(diǎn):
- 內(nèi)存無(wú)碎片。
- 同時(shí)避免了當(dāng)有用對(duì)象比較多的時(shí)候,復(fù)制回收算法的麻煩。
分代回收算法

新生代:
剛創(chuàng)建的對(duì)象都在新生代,新生代采用復(fù)制回收算法。新生代分為三個(gè)區(qū),一個(gè)Eden區(qū),一般兩個(gè)Survivor區(qū)。大部分對(duì)象在Eden區(qū)生成,當(dāng)Eden區(qū)域滿時(shí),將還存活的對(duì)象復(fù)制到其中一個(gè)Survivor區(qū)域,當(dāng)這個(gè)Survivor區(qū)域滿時(shí),將其中還存活的對(duì)象復(fù)制到第二個(gè)Survivor區(qū)域。那么當(dāng)?shù)诙€(gè)Survivor區(qū)域滿時(shí)該怎么辦呢?那就是將第二個(gè)Survivor區(qū)域中由第一個(gè)Survivor區(qū)域復(fù)制過(guò)來(lái)的對(duì)象,復(fù)制到“老年代”中。
這個(gè)過(guò)程是有點(diǎn)繞,但是可以想象成面試過(guò)程中層層選拔的過(guò)程,能力越強(qiáng)的可以想象成生命周期越長(zhǎng)的對(duì)象。
老年代:
這個(gè)區(qū)域中的對(duì)象都是在新生代中經(jīng)歷了層層回收后仍然存活的對(duì)象,這個(gè)區(qū)域采用標(biāo)記整理的算法進(jìn)行垃圾回收。
持久代:
持久代中用于存放一些靜態(tài)文件,static常亮,常量池等。這塊區(qū)域?qū)厥諞](méi)有顯著影響。
什么時(shí)候會(huì)進(jìn)行垃圾回收?
GC有兩種類型:Minor GC和Full GC。
Minor GC
當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Minor GC,對(duì)Eden區(qū)域進(jìn)行GC,清理非存活對(duì)象。
Full GC
對(duì)整個(gè)堆進(jìn)行整理,所以比Minor要慢,所以盡可能地減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過(guò)程中,很大一部分工作就是對(duì)于Full GC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:
- 老年代被寫滿;
- 持久代被寫滿;
- System.gc()被顯式調(diào)用。
參考資料:《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第二版)》