深入理解Java虛擬機03--垃圾收集器與內存分配策略
一.概述
- 哪些內存需要回收?
- 什么時候回收?
- 如何回收?
二.對象已死嗎
1.引用計數算法
-
定義:給對象添加一個引用計數器,當增加一個引用時,加1,當一個引用時,減1;
-
缺陷:當對象之間互相循環引用時,就會變的像“不死對象”;
2.可達性分析算法
在主流的商用程序語言(Java、C#,甚至包括前面提到的古老的Lisp)的主流實現中, 都是稱通過可達性分析(Reachability Analysis)來判定對象是否存活的。這個算法的基本思 路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所 走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連 (用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。如 圖3-1所示,對象object 5、object 6、object 7雖然互相有關聯,但是它們到GC Roots是不可達 的,所以它們將會被判定為是可回收的對象

在Java語言中,可作為GC Roots的對象包括下面幾種:
虛擬機棧(棧幀中的本地變量表)中引用的對象。
方法區中類靜態屬性引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI(即一般說的Native方法)引用的對象。
3、引用
- 強引用:“=”實現,引用只要還在,就不會被GC回收;
- 軟引用:SoftReference實現,在內存不足發生OOM之期,就回收掉該引用;
- 弱引用:WeakReference實現,在下一次GC前,就回收掉該引用;
- 虛引用:PhantomReference實現,在任何時候可能被回收,不過回收后可以收到系統的通知;
4.生存還是死亡 (finalize方法自救)
- 如果GC Roots 不可達,進行第一次標記;
- 再看finalize()方法有沒有被復寫,有則先執行finalize()方法;
- 逃命方法:將自己關聯到類變量之類或者某對象的成員變量的;
- 終身只有一次調用機會;
- 接著GC進行第二次標記,如果沒有逃脫,則回收;
5.無用的類
- 所有該類的實例已被回收
- 該類的ClassLoad已被回收
- 該類對應的java.lang.Class沒有在任何地方被引用,不存在該類的反射
三.垃圾回收算法
1、標記——清除法
- 方案:先標記,然后直接回收
- 缺陷:產生大量碎片,不好分配大內存
2、復制算法
- 方案:將內存分為AB兩塊,回收時,將A塊的對象全部復制到B,然后把未回收的上移,然后把已使用過的A全部清除;
- 優點:實現簡單,運行高效;
- 缺陷:有一半處于無用狀態,浪費;
- HotSpot:通過 Eden:Surivor:Surivor=8:1:1的比例分配,這樣只有10%的空間浪費;不足時,需要依賴其他內存分配擔保;
3、標記—整理算法
- 方案:標記—清理之后,再整體上移;
- 適應:適合老年代內存區使用
4、分代收集算法
- 新生代:使用復制算法
- 老年代:使用“標記-清理”或者“標記—整理”算法
四.垃圾收集器
1、 Serial收集器
- 這個收集器是一個單線程收集器,
- 只會使用一個CPU或者一條收集線程進行垃圾收集工作
- 其余的工作現場必須暫停,直到收集結束
2、ParNew收集器
- Serial收集器的多線程版本
- 垃圾收集器線程和工作線程同時工作
3、Paraller Scavenge收集器
- 目標:達到一個可控的吞吐量
- 吞吐量: 用戶運行時間/(用戶運行時間+垃圾回收時間)
- GC自適應調節:調整參數提供最合適的停頓時間或者最大的吞吐量
4、CMS收集器
- 目標:最短回收停頓時間
- 步驟:
- 初始標記
- 并發標記
- 重新標記
- 并發清除
- 缺點:
- 對CPU資源非常敏感
- 無法處理浮動垃圾
- 基于“標記-清除”算法實現,碎片多
5、G1 收集器
- 并行與并發
- 分代收集
- 空間整合:
- 整體看:基于“標記-整理”算法
- 局部看:基于“復制”算法
- 可預測停頓
- 有計劃避免 Java 堆中進行全區域的垃圾收集
6、GC日志
33.125: [GC [DefNew: 3324k ->152k(3712k),0.0025925 secs] 3324k->152k(11904k),0.0031680 secs]
- 33.125: GC發生時間
- [GC:GC停頓類型,如果是 [Full GC
- [DefNew: 垃圾回收器類型
- 3324k ->152k(3712k): GC前內存區域已使用容量-> GC后內存區域已使用容量(內存區域總容量)
- 3324k->152k(11904k): GC前Java堆已使用容量->GC后Java堆已使用容量
- 0.0025925:內存區域GC所占用時間
五.內存分配和回收策略
最終都是給對象分配內存和回收分配給對象的內存
- 對象優先在 Eden 分配
- 大對象直接進入老年代
- 長期存活的對象進入老年代
- 動態對象年齡判定
如果在一個 Survivor 空間中相同年齡所占內存大小占有一半,那么大于或者等于該年齡的對象直接進入老年代
- 空間分配擔保
新生代的回收采用“復制算法”,如果沒有足夠的內存空間,需要老生代來擔保,將一部分對象直接進入老年代,如果老年代空間還是不足,就有危險了。所以就根據以前進入老年代的對象容量大小的平均值來做個參考。通過 Fulll GC 對老年代進行一次GC,盡量騰出更多的空間。以防擔保失敗。
- Minor Gc 和 Full GC
- Minor Gc:新生代的GC,很快
- Full GC:老年代的GC,慢10倍
六.小結
本篇了解引用的算法,由此去判斷一個對象是"死”還是“活”,四種引用類型,由強至弱,生存能力越來越差,被 GC 回收的可能性也就越高。當然在一生中有唯一一次的自救機會,就是復寫 finalize() 方法。進行垃圾回收時,會影響到性能。所以,出了各種回收算法以及垃圾回收器。這些各有優劣,只有適合當前環境的才是最好的。
浙公網安備 33010602011771號