深入理解Java虛擬機-垃圾回收

1. 如何判斷對象爲垃圾對象

1.1 引用計數法

思路:在對象中添加一個引用計數器,當有地方引用這個對象時,引用計數器的值就加1,當引用失效時,引用計數器就減1。
棧中變量引用對象,引用計數器加1
棧中變量引用失效,引用計數器減1
相互引用導致雙方的引用計數器均爲1,不能被垃圾回收

package gc;

public class GCtest {

    private Object instance;

    public static void main(String[] args) {

        GCtest g1 = new GCtest();
        GCtest g2 = new GCtest();

        g1.instance = g2;
        g2.instance = g1;

        g1 = null;
        g2 = null;

        System.gc();
    }
}

加入虛擬機參數-verbose:gc -XX:+PrintGCDetails打印垃圾回收信息

[0.155s][info   ][gc,heap        ] GC(0) Eden regions: 3->0(2)
[0.155s][info   ][gc,heap        ] GC(0) Survivor regions: 0->0(0)
[0.155s][info   ][gc,heap        ] GC(0) Old regions: 0->2
[0.155s][info   ][gc,heap        ] GC(0) Humongous regions: 0->0
[0.155s][info   ][gc,metaspace   ] GC(0) Metaspace: 608K->608K(1056768K)
[0.155s][info   ][gc             ] GC(0) Pause Full (System.gc()) 2M->0M(8M) 5.209ms

證明垃圾被回收了,說明虛擬機採用的不是引用計數法。下面介紹可達性分析法。

1.2 可達性分析法

思路:通過一系列的的成爲“GC Roots”的對象作爲起始點,從這些結點開始向下搜索,搜索過的路徑成爲引用鏈,當一個對象到GC Roots沒有任何引用鏈項鍊,則證明此對象是不可達的。
可達性分析法
Object5,6,7是不可達的,故它們爲可回收對象
可作爲GC Roots的對象:
1.虛擬機棧(棧幀中的局部變量表)中引用的對象
2.本地方法棧JNI引用的對象
3.方法區中的靜態屬性引用的對象
4.方法區中常量引用的對象

主流垃圾收集器所使用的方法均是可達性分析法

1.3 四種引用

強引用:只要強引用存在,垃圾收集器永遠不會回收掉被引用的對象
軟引用:軟引用關聯的對象,需要在內存不夠時被垃圾回收
弱引用:弱引用引用的對象,只能生存到下一次垃圾回收之前。
虛引用:虛引用是一種最弱的引用關係,一個對象是否有虛引用的存在,不會對其生存時間構成影響,也無法通過虛引用來獲取一個引用實例,設置虛引用的目的是能在這個對象被垃圾回收時收到一個系統通知。

2. 如何回收

2.1 回收策略

2.1.1 標記-清除算法

算法分爲標記和清除兩個階段,收i按表基礎所有需要回收的對象,在標記完成後統一回收所有被標記的碎片。
在這裏插入圖片描述
主要問題:
1.效率問題:標記和清楚兩個過程的效率不高,
2.會產生內存碎片:標記清除後會產生大量不連續的內存雖貧,導致在以後分配大對象時提前觸發垃圾收集。

2.1.2 標記-複製算法

HotSpot將新生代內存分爲一個較大的Eden區域(80%)和兩個較小的Survivor區域(10%+10%)。每次使用Eden和一個Survivor,當回收時,將Eden和Survivor中還存活的對象一次性的複製另一個Survivor區域,最後清理掉Eden和剛纔用過的Survivor區域。
當Survivor空間不夠用時,需要依賴老年代進行內存擔保。
標記複製算法

2.1.3 標記-整理算法

標記-整理算法不直接對可回收對象進行清理,而是讓所有存活的對象向一端移動,然後直接清理掉端邊界以外的內存。
標記整理算法

2.1.4 分代收集算法

根據對象存活週期將堆內存分爲新生代和老年代,在新生代使用標記-複製算法,在老年代使用標記-清除或標記-整理算法進行回收。

2.2 垃圾收集器

HotSpot中的垃圾收集器
併發:指多條垃圾收集線程並行工作,此時用戶線程處於等待狀態
並行: 指用戶線程和垃圾收集線程同時執行,用戶程序在繼續運行,而垃圾收集程序運行於另一個CPU上

2.2.1 Serial

Serial是一個單線程的收集器,在進行垃圾收集時需要暫停其他工作的線程,是虛擬機運行在Client模式下的新生代的默認新生代收集器。Serial簡單高效,在單個 CPU環境下,他由於沒有線程間交互和開銷,可以獲得最高的單線程收集效率。
在這裏插入圖片描述

2.2.2 Parnew

ParNew是Serial的多線程版本,是運行在Server模式下的新生代收集器,可以與CMS垃圾收集器配合使用
在這裏插入圖片描述

2.2.3 Parallel Scavenge

Parallel Scavenge收集器是一個新生代收集器,它的目標是達到一個可控制的吞吐量。Parrallel Scavenge收集器提供了兩個參數來精確控制吞吐量,最大垃圾收集停頓時間的-XX:MaxGCPauseMillis,以及設置吞吐量大小的-XX:GCTimeRatio。GC停頓時間縮短是以犧牲吞吐量和新生代空間換取的,所以並不是把MaxGCPauseMillis設置的越小越好。
在這裏插入圖片描述

2.2.4 Serial Old

Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用的是標記-整理算法。主要用於Client模式下的虛擬機使用,如果用在Server模式下可以與Parallel Scavenge收集器搭配使用,也可以作爲CMS再發生Concurrent Mode Failure時後使用。
在這裏插入圖片描述

2.2.5 Parallel Old

Parallel Old收集器是Parallel Scavenge的老年代版本。在注重吞吐量優先和CPU資源敏感的場合,可以考慮使用Parallel Old收集器。

2.2.6 CMS

CMS收集器是一種以獲取最短回收停頓時間爲目標的收集器。採用的是標記-清除算法,整個過程分爲下列四個步驟:
1.初始標記:僅僅標記GC Roots能關聯的對象,需要停頓。
2.併發標記:進行GC RootsTracing的過程,不需要停頓。
3.重新標記:是爲了修正併發標記期間因用戶線程運作而導致的標記產生變動的那一部分對象的標記記錄,需要停頓。
4.併發清除:清除用戶線程產生的垃圾,不需要停頓。

CMS收集器的缺點:
1.對CPU資源敏感,默認回收線程爲(CPU數量+3)/4。
2.無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗導致另一次Full GC的產生,由於在併發清除的過程中,仍有用戶線程在工作,所以要在老年代中預留一部分空間提供併發清除時程序使用
3.採用的是標記清除-算法,在垃圾收集過後可能吹按大量的內存碎片,會給大對象帶來很大的麻煩,沒有連續的內存空間來分配大對象,不得不提前觸發Full GC。
在這裏插入圖片描述

2.2.7 G1

G1收集器是同時運行在新生代和老年代的垃圾收集器,它有如下特點:
1.併發與並行:使用多個CPU來縮短停頓時間
2.分代收集:採取不同的方式來處理新創建的對象和已經存活一段時間,經過多次GC的舊對象。
3.空間整合:從整體來看是基於標記-整理算法,從局部來看是基於標記-複製算法。
4.可預測的停頓:能讓使用者明確指定在一個長度爲M毫秒的時間片段內,消耗在垃圾停頓的時間不能超過N毫秒。

在使用G1收集器時,Java堆被劃分成多個大小相等的獨立區域(Region),新生代和老年代不在物理隔離,他們是一部分Region的集合。G1跟蹤各個Region的垃圾堆積價值的大小,在後臺維護一個優先列表,每次根據允許回收的時間,優先回收價值最大的Region。虛擬機使用Remerbered Set來避免全堆掃描。

整個過程分爲下列四個步驟:
1.初始標記:僅僅標記GC Roots能關聯的對象,並且修改TAMS的值,讓下一階段用戶程序併發運行時,能在正確可用的REgion中創建新對象,需要停頓。
2.併發標記:進行GC RootsTracing的過程,不需要停頓。
3.重新標記:是爲了修正併發標記期間因用戶線程運作而導致的標記產生變動的那一部分對象的標記記錄,需要停頓。
4.篩選回收:首先對各個Region的回收價值和成本進行排序,根據用戶期望的GC停頓時間來制定回收計劃。

在這裏插入圖片描述

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