1. 程式人生 > >深入JAVA虛擬機之垃圾收集

深入JAVA虛擬機之垃圾收集

收集 ESS 解釋 美的 路徑 平靜的 過程 對象創建 image

前言:

說起垃圾收集器,JAVA開發者肯定是聽得耳朵都起繭子了。如果讓你設計一個JAVA垃圾收集器,那麽你關註那些點呢?

// 1.哪些內存需要回收?
// 2.什麽時候回收?
// 3.如何回收?

這篇博文就是記錄這些問題答案的。閑言碎語不多講,開始寫。


那些內存需要回收?

我們先來回顧一下“運行時數據區”的知識點。我們都知道程序計數器虛擬機棧本地方法棧都是與線程同生共死的。棧中的棧幀分配多少內存在類結構確定下來時就已經確定了。所以它們的內存分配和回收都是非常確定性的,因為方法結束或者線程結束內存自然就回收了。
但是Java堆方法區就不一樣了,一個接口中的多個實現類需要的內存可能不一樣,一個方法的多個分支需要的內存也可能不一樣,只有在程序執行期間才能知道會創建哪些對象。這部分內存的分配和回收都是動態的,垃圾收集器關註的就是這部分內存。

什麽時候回收?

java堆中幾乎存著所有的java世界中的對象實例,垃圾收集器對堆進行回收前,首先要做的事情就是:確定不可能再被任何途徑使用的對象。這件事當然是交給算法去做,引用計數算法根搜索算法

  1. 引用計數算法

給對象添加一個引用計數器,每當有一個地方引用它時,計數器加1,當引用失效時,計數器值減1.如果任何時候計數器都為0的對象就是不可能再被使用的。書中說實際上java虛擬機沒有使用這種算法去管理內存,因為它很難解決對象間相互循環引用的問題。比如說:A對象,B對象,他們相互引用,那麽他們的引用計數器就不會為0,那麽GC就永遠沒法回收它們,實際上他們已經可以被回收了。這個案例也可以java虛擬機到底是不是使用的引用計數器算法。

2.根搜索算法
算法基本思路是:通過一系列的名為“GC Roots”的對象為起點,從這些節點開始向下搜素,搜素所有走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何鏈路時,則說明該對象是不可用的。

3.引用
如果reference類型的數據中存放的數值是代表另一塊內存的起始地址,我們就說這塊內存代表著一個引用。引用分類如下:
強引用:(如Object obj = new Object())、永遠不會被垃圾收集器掉引用的對象。

軟引用:描述還有用,但非必須的對象。在系統面臨內存溢出風險前,將把這些加入到回收範圍進行二次回收。

弱引用:描述非必需的對象,只能生存到下一次垃圾收集發生之前。

虛引用:幽靈引用,不會對生存時間構成影響,也無法通過虛引用獲取對象實例。唯一目的就是在回收時,能夠收到一個系統通知。

如何回收

垃圾收集算法
上面講了通過算法判斷是否將對象列入回收名單中,那麽接下來就是講解垃圾收集算法。介紹幾種算法的思路和發展過程,具體實現不在此詳細說明。
標記-清除算法
分兩個階段:標記、清除。將所需要回收的對象進行標記,在標記完成後,統一回收掉所有被標記的對象。所謂標記也就是前面說說的內容,如何判斷一個對象已經“死了”。這個算法效率不高,因為兩個階段效率都不高,而且在回收完成後,會產生大量不連續的內存碎片,當需要給大對象進行內存分配時不得不觸發另一次垃圾回收動作。

復制算法
為了解決效率問題,一種稱為復制的收集算法出現了,它將可用內存按照容量劃分為兩個大小相等的區域,每次只使用其中一塊,當一塊用完以後,就將還存活的對象復制到另外一塊上,然後一次性清理掉當前滿了的區塊。

標記-整理算法
復制算法當對象的存活率很高時,需要大量的復制對象,從而降低效率,而且該種方式降低了可用內存大小。標記-清理算法和標記-整理算法前面階段是相同的,在標記完成以後,不做清理,而是將存活對象,往一邊移動,使得會搜狐的內存能夠連續在一起。

備註:
沒有完美的算法,只有適用場景的算法。上述的算法都有各自的優缺點,只用一種算法都無法很好的覆蓋虛擬機中內存回收的場景。所以出現了分代回收算法。其實這個也沒有什麽新意,就是把對象根據存活周期劃分不同的內存區域,從而針對不同的內存區域進行不同算法的垃圾收集。

分代回收算法
一般java堆中分為新生代和老年代。根據各個分代的特點采用合適的算法進行回收。新生代中對象的創建率很高,死亡率也很高,所以適合采用復制算法,只要付出少量對象的復制成本就可以完成回收。而老年代中對象創建率低,而且存活率極高,所以使用標記-整理算法是在合適不過了。

備註:
java堆劃分內存區域進行內存回收的方式可以更加細致一些,Eden Space、Survivor Space、Tenured Gen;
下面這裏是一種比較形象容易記住的方式進行說明,來自分代回收通俗解釋
1、一個人(對象)出來(new 出來)後會在Eden Space(伊甸園)無憂無慮的生活,直到GC到來打破了他們平靜的生活。GC會逐一問清楚每個對象的情況,有沒有錢(此對象的引用)啊,因為GC想賺錢呀,有錢的才可以敲詐嘛。然後富人就會進入Survivor Space(幸存者區),窮人的就直接kill掉。
2、 大對象直接近入老年代-養老區:有些世界土豪出生。 他父母直接砸了幾百億, 身份顯赫, 進入老年代,有錢就是囂張!不用去Eden Space(伊甸園)。
3、Survivor Space(幸存者區有兩個區域:生活區和無人區)為什麽有兩個區 from Survivor(生活區) ,to Survivor(無人區) .每次GC想要去幸存者區敲詐 ,會去from Survivor(生活區的)所有人帶到to Survivor(無人區) ,然後開始敲詐, 被敲詐包括本次15次 的土豪,進入養老區,交不起保護費 的殺死, 沒滿足15 次,但是手裏還有點錢的就生活在無人區,這個無人區就變成了生活區, 以前的生活區(人都被移走了) 又變成了無人區
4、並不是進入Survivor Space(幸存者區)後就保證人身是安全的,但至少可以活段時間。
GC會定期(可以自定義)會對這些人進行敲詐,億萬富翁每次都給錢,GC很滿意,就讓其進入了Genured Gen(養老區)。
(每經過一次Minor GC,會給這個富翁添加一次記錄,當某些富翁連續給了大概15 年保護費,就可以去養老區了)萬元戶經不住幾次敲詐就沒錢了,GC看沒有啥價值啦,就直接kill掉了。
5、進入到養老區的人基本就可以保證人身安全啦,但是億萬富豪有的也會揮霍成窮光蛋,只要錢沒了,GC還是kill掉。

擴展:垃圾收集器

算法是內存回收的方法論,而垃圾收集器就是內存回收的具體實現了,JAVA虛擬機規範中沒有任何關於垃圾收集器該如何實現的規定,因此不同廠商,不同版本實現的垃圾收集器有很大差別。下圖是1.6HotSpot JVM的垃圾收集器。
技術分享圖片

備註:
如果兩個垃圾收集器之間有聯系說明,它們可以配合使用。各個垃圾收集器的實現內容自行查閱資料,我就不再這裏碼字。下面是關於垃圾收集相關的一些參數,對於調優具有很好的作用。
技術分享圖片

深入JAVA虛擬機之垃圾收集