JVM:三、垃圾回收器與內存分配簡介

目錄:         

         對象已死嗎?

垃圾回收算法

垃圾收集器

內存分配與回收策略


對象已死嗎?

如何判斷對象已經死了呢,如下:

1. 引用計數算法

    引用計數算法給對象添加一個引用計數器,每當一個地方引用它是時,計數值就增加一;當引用失效時,計數值就建一,技術器爲0的對象不可能在被使用。但是主流的JVM裏沒有使用它的,原因是他很難解決對象之間互相循環引用的問題。

    例如對象objA和objB都有字段instance,objA.instance = objB,objB.instance = objA,除此之外這兩個對象再無任何引用,實際上這兩個對象已經不可能在被訪問,但是因爲相互引用,所以計數值不爲0,無法收集他們。

2. 可達性分析算法

    這個算法的基本思路就是通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何的引用鏈相連時,證明此對象是不可用的,如圖:

雖然對象object5,object6,object7是相互關聯的,但是他們到GC Roots不可達,所以是可回收的。

可以作爲gc root的有:

1 、 虛擬機棧(棧幀中的本地變量表)中引用的對象。

2、 本地方法棧中JNI(即一般說的native方法)引用的對象。

3、 方法區中的靜態變量和常量引用的對象。

垃圾回收算法

1. 標記-清除(Mark-Sweep)算法

    如同其名,它的回收有兩個階段,首先標記出所有的需要回收的對象,在標記完成後統一回收所有被標記的對象。它是一種最基本的回收方法,但他也有不足。一是效率不高,標記和清除的效率都不高,二是容易產生內存碎片(如圖),碎片太多導致無法分配較大的對象時,就會提前觸發另一次垃圾收集動作。後續的方法都是在它的基礎上進行改進。

2. 複製算法

    將內存的區域劃分爲相同大小的兩塊,每次只是用一塊,在一塊使用完成後,將其中的存活對象存儲到另一塊中,再把已使用的內存空間全部清理掉,這樣就不用考慮內存碎片化的問題了,只需要移動堆頂的指針就行。但是這樣的缺點就是隻有一般半的內存可用。

    複製算法也是新生代使用的回收算法,IBM的一項研究表明,新生代中的對象98%是“朝生夕死”的,也就是存活對象只佔了大概2%。所以沒有必要按照1:1分配空間,而是將內存劃分爲一塊較大的Eden空間和兩塊小的Survivor空間,每次使用Eden和一塊Survivor。回收時,將Eden和Survivor中還存活的對象一次性的複製到另一塊Survivor中,最後清理掉Eden和使用的Survivor。

    HotSpot虛擬機默認Eden和Survivot的內存大小比例爲8:1,所以整個新生代中可用的空間容量爲新生代總容量的90%。當然無法保證每次存活的對象只佔不到10%,這時就可以通過分配擔保機制使用老年代來繼續存儲。

3. 標記-整理(Mark-Compact)算法

    進入老年代的對象一般都是存活率較高的,假如在一次對象回收的過程中全部的對象都存活,這時如果再用複製算法,那麼需要的空間將是50%,浪費極高。所以老年代使用和標記-清除類似的方式,標記過程一樣,但是後續操作不是直接清理,而是將可回收對象移動到內存的一端,然後清理掉另一端的其他剩餘內存。如圖:

4. 分代收集算法

    這算是對GC垃圾收集算法的一個總結,當前虛擬機大都採用分代收集算法(),當前堆中根據對象的存活週期將內存劃分爲了幾塊,一般分爲新生代和老年代。新生代中大量的對象都會死去,所以使用複製算法複製少量的對象;而老年代中對象存活效率高,沒有額外的空間進行分配擔保而且爲了效率,就必須使用標記-整理算法。

 

垃圾收集器

 垃圾收集器的演化歷史:Serial  -> Parallel(並行) -> CMS(併發) -> G1

總之,就是爲了減少stop the world的時間甚至是達到併發的效果。

並行:多條垃圾收集線並行工作,但是用戶線程處於等待狀態。

併發:指用戶線程和垃圾回收線程可以同時執行。

像Serial,Parallel這種是使用指針碰撞(即連續的內存地址)分配內存的,而CMS是使用空閒列表(即分散的內存地址)分配內存的。

 

Serial收集器: 新生代單線程收集器,在他進行回收時,必須暫停其他的工作進程。

Parallel收集器: 用在新生代,是Serial的多線程版本

Serial Old收集器: 老年代單線程

Parallel Old收集器: 老年代多線程

Parallel Scavenge收集器: 新生代吞吐量優先回收器,不需要設置SurvivorRatio,Xmn,晉升老年代大小。都會自動調整。但是無法和CMS匹配使用

CMS(Concurrent Mark Sweep)收集器:用在老年代,標記-清除方式,希望停頓的時間最短可以用這個

初始標記 -> 併發標記 -> 重新標記 -> 併發清除  四個階段。只有初始標記和重新標記時需要stop the world。所以停止時間最短,但是在程序運行時就一直在標記,所以對CPU很敏感會佔用CPU。另外,CMS是基於標記-清除算法的,所以會生成很多碎片。

G1(Garbage-first)收集器:最新的收集器,未來替代CMS的收集器

G1的優點:

  • 支持並行和併發
  • 分代收集,G1既可以支持新生代回收也可以支持老年代回收,
  • 空間整合,不同於CMS的標記-清除,G1使用的標記-整理算法,所以內存碎片生成的要少。

 

內存分配與回收策略

虛擬機給每個對象定義了一個對象年齡計數器,在對象在Eden創建並經過第一次Minor GC後仍然存活,並能被Suivivor容納的話,將會被移動到Survivor空間,並對象年齡設置爲1。每經歷過Minor GC,年齡就增加1歲,當到一定程度(默認15歲,可以通過參數-XXMaxTenuringThreshold設置),就將會晉升年老代。 

 

Full GC頻繁原因:

在 CMS 啓動過程中,新生代提升速度過快,老年代收集速度趕不上新生代提升速度

在 CMS 啓動過程中,老年代碎片化嚴重,無法容納新生代提升上來的大對象

 

如何從新生代進入老年代:

1,大對象直接存入老年代,爲了減少full gc,這個大小值可以設置。

2,長期存活的對象直接存入老年代,例如年齡超過15了,這個年齡值可以設置。

3,動態年齡判定:同等年齡的超過survivor的一半,大於這個年齡的進入老年代。

 

空間分配擔保:如果老年代最大的連續空間小於新生代的所有的對象之和,那麼擔保失敗,就會啓動一次Full GC。

 

參考:《深入理解Java虛擬機》

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章