通常有兩種方式(引用計數法、可達性分析),JVM使用的是可達性分析算法
給對象添加一個引用計數器,當對象增長一個引用時計數器加 1,引用失效時計數器減 1。引用計數爲 0 的對象可被回收(Python 在用,但主流虛擬機沒有使用)緩存
來斷定對象是否存活的。這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是不可用的bash
做爲 GC Roots 的對象包括下面幾種:服務器
Java提供finalize()方法,垃圾回收器準備釋放內存的時候,會先調用finalize(),能夠完成對象的拯救(不被回收),可是不能保證必定不被回收,說白了就是沒啥用,一個坑多線程
Reference 中存儲的數據表明的是另外一塊內存的起始地址併發
通常的 Object obj = new Object() ,就屬於強引用。 (若是有 GCroots 的強引用)垃圾回收器絕對不會回收它,當內存不足時寧願拋出 OOM 錯誤,使得程序異常中止,也不會回收強引用對象jvm
SoftReference垃圾回收器在內存充足的時候不會回收它,而在內存不足時會回收它網站
示例代碼:spa
public static void main(String[] args) {
String str = new String("SunnyBear"); // 強引用
SoftReference<String> strSoft = new SoftReference<String>(str);
str = null; // 幹掉強引用,確保只有strSoft的軟引用
System.out.println(strSoft.get()); // SunnyBear
System.gc(); // 執行一次gc,此命令請勿在線上使用,僅做示例操做
System.out.println("------------ gc after");
System.out.println(str); // null
System.out.println(strSoft.get()); // SunnyBear
}
複製代碼
因此軟引用通常用來實現一些內存敏感的緩存,只要內存空間足夠,對象就會保持不被回收掉線程
垃圾回收器在掃描到該對象時,不管內存充足與否,都會回收該對象的內存
示例代碼:
public static void main(String[] args) {
String str = new String("SunnyBear"); // 強引用
WeakReference<String> strWeak = new WeakReference<String>(str);
str = null; // 幹掉強引用,確保只有strSoft的軟引用
System.out.println(strWeak.get()); // SunnyBear
System.gc(); // 執行一次gc,此命令請勿在線上使用,僅做示例操做
System.out.println("------------ gc after"); // null
System.out.println(str); // null
System.out.println(strWeak.get()); // null
}
複製代碼
實際應用,如WeakHashMap、ThreadLocal
幽靈引用,最弱,被垃圾回收的時候收到一個通知,若是一個對象只具備虛引用,那麼它和沒有任何引用同樣,任什麼時候候均可能被回收
虛引用主要用來跟蹤對象被垃圾回收器回收的活動
將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要按順序分配內存便可, 實現簡單,運行高效。只是這種算法的代價是將內存縮小爲了原來的一半。
注: 專門研究代表,新生代中的對象 90%是「朝生夕死」的,因此通常來講回收佔據 10% 的空間夠用了,因此並不須要按照 1:1 的比例來劃份內存空間,而是 將內存分爲一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor[1]。當回收時,將 Eden 和 Survivor 中還存活着的對象一 次性地複製到另一塊 Survivor 空間上,最後清理掉 Eden 和剛纔用過的 Survivor 空間。 HotSpot 虛擬機默認 Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代中可用內存空間爲整個新生代容量的 90%(80%+10%),只有 10%的內存會被 「浪費」。
首先標記全部須要回收的對象,而後統一回收被標記的對象
首先標記出全部須要回收的對象,在標記完成後,後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端,邊界之外的內存
jvm 垃圾回收器把上面的三種算法所有用到了,採用分代收集
收集器 | 收集對象和算法 | 收集器類型 |
---|---|---|
Serial | 新生代,複製算法 | 單線程 |
ParNew | 新生代,複製算法 | 並行的多線程收集器 |
Parallel Scavenge | 新生代,複製算法 | 並行的多線程收集器 |
收集器 | 收集對象和算法 | 收集器類型 |
---|---|---|
Serial Old | 老年代,標記整理算法 | 單線程 |
Parallel Old | 老年代,標記整理算法 | 並行的多線程收集器 |
CMS(Conc Mark Sweep ) | 老年代,標記清除算法 | 並行和併發收集器 |
G1(Garbage First) | 跨新生代和老年代,複製算法 + 標記整理算法 | 並行和併發收集器 |
注:
jps -v
能夠看到使用的垃圾收集器,例如:-XX:+UseConcMarkSweepGC
(CMS)連線表示能夠 新生代 和 老年代 配套使用的垃圾收集器
最古老的,單線程,獨佔式,成熟,適合單 CPU 服務器 -XX:+UseSerialGC 新生代和老年代都用串行收集器
ParNew 和 Serial 基本沒區別,惟一的區別:多線程,多 CPU 的,停頓時間比 Serial 少
-XX:+UseParNewGC 新生代使用 ParNew,老年代使用 Serial Old
能夠和CMS搭配使用
關注吞吐量的垃圾收集器,高吞吐量則能夠高效率地利用 CPU 時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。所謂吞吐量就是 CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了 100 分鐘,其中垃圾收集花掉 1 分鐘,那有吞吐效率就是 99%
收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的 Java 應用集中在互聯網站或者 B/S 系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。
CMS 收集器就很是符合這類應用的需求。-XX:+UseConcMarkSweepGC
,通常新生代使用 ParNew,老年代的用 CMS,從名字(包含「Mark Sweep」)上就能夠看出,CMS 收集器是基於「標記—清除」算法實現的,它的運做過程相對於前面幾種收集器來講更復雜一些
整個過程分爲 4 個步驟,包括:
G1相比較CMS的改進
可預測的停頓:
G1 收集器之因此能創建可預測的停頓時間模型,是由於它能夠有計劃地避免在整個 Java 堆中進行全區域的垃圾收集。G1 跟蹤各個 Region 裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的 Region(這也就是 Garbage-First 名稱的來由)。這種使用 Region 劃份內存空間以及有優先級的區域回收方式,保證了 G1 收集器在有限的時間內能夠獲取儘可高的收集效率
G1 把堆劃分紅多個大小相等的 獨立區域(Region),新生代和老年代再也不物理隔離
G1 算法將堆劃分爲若干個獨立區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停全部應用線程的方式,將存活對象拷貝到老年代或者 Survivor 空間。例如其中一個獨立區域如圖:
1. Young GC
Young GC 主要是對 Eden 區進行 GC,它在 Eden 空間耗盡時會被觸發。在這種狀況下,Eden 空間的數據移動到 Survivor 空間中,若是 Survivor 空間不夠,Eden 空間的部分數據會直接晉升到老年代空間。Survivor 區的數據移動到新的 Survivor 區中,也有部分數據晉升到老年代空間中。最終 Eden 空間的數據爲空,GC 中止工做,應用線程繼續執行
2. Mixed GC
選定全部新生代裏的 Region,外加根據 global concurrent marking 統計得出收集收益高的若干老年代 Region。在用戶指定的開銷目標範圍內儘量選擇益高的老年代 Region。Mixed GC 不是 full GC,它只能回收部分老年代的 Region。若是 mixed GC 實在沒法跟上程序分配內存的速度,致使老年代填滿沒法繼續進行 Mixed GC,就會使用 serial old GC(full GC)來收集整個 GC heap。因此咱們能夠知道,G1 是不提供 full GC 的
大體分爲4個步驟:
參數 | 描述 |
---|---|
UseSerialGC | 虛擬機運行在 Client 模式下的默認值,打開此開關後,使用 Serial+Serial Old 的收集器組合進行內存回收 |
UseParNewGC | 打開此開關後,使用 ParNew + Serial Old 的收集器組合進行內存回收 |
UseConcMarkSweepGC | 打開此開關後,使用 ParNew + CMS + Serial Old 的收集器組合進行內存回收。Serial Old 收集器將做爲 CMS 收集器出現 Concurrent Mode Failure 失敗後的後備收集器使用 |
UseParallelGC | 虛擬機運行在 Server 模式下的默認值,打開此開關後,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器組合進行內存回收 |
UseParallelOldGC | 打開此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收 |
SurvivorRatio | 新生代中 Eden 區域與 Survivor 區域的容量比值,默認爲 8,表明 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold | 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配 |
MaxTenuringThreshold | 晉升到老年代的對象年齡,每一個對象在堅持過一次 Minor GC 以後,年齡就增長 1,當超過這個參數值時就進入老年代 |
UseAdaptiveSizePolicy | 動態調整 Java 堆中各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure | 是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個 Eden 和 Survivor 區的全部對象都存活的極端狀況 |
ParallelGCThreads | 設置並行 GC 時進行內存回收的線程數 |
GCTimeRatio GC | 時間佔總時間的比率,默認值爲 99,即容許 1% 的 GC 時間,僅在使用 Parallel Scavenge 收集器生效 |
MaxGCPauseMillis | 設置 GC 的最大停頓時間,僅在使用 Parallel Scavenge 收集器時生效 |
CMSInitiatingOccupancyFraction | 設置 CMS 收集器在老年代空間被使用多少後觸發垃圾收集,默認值爲 68%,僅在使用 CMS 收集器時生效 |
UseCMSCompactAtFullCollection | 設置 CMS 收集器在完成垃圾收集後是否要進行一次內存碎片整理,僅在使用 CMS 收集器時生效 |
CMSFullGCsBeforeCompaction | 設置 CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理,僅在使用 CMS 收集器時生效 |