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

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

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

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

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

優(yōu)點:
- 內(nèi)存無碎片。
- 同時避免了當有用對象比較多的時候,復制回收算法的麻煩。
分代回收算法

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