程序計數器、虛擬機棧和本地方法棧這三個區域屬於線程私有的,只存在於線程的生命週期內,線程結束以後也會消失,所以不須要對這三個區域進行垃圾回收。垃圾回收主要是針對 Java 堆和方法區進行。html
給對象添加一個引用計數器,當對象增長一個引用時計數器加 1,引用失效時計數器減 1。引用計數爲 0 的對象可被回收。java
兩個對象出現循環引用的狀況下,此時引用計數器永遠不爲 0,致使沒法對它們進行回收。git
public class ReferenceCountingGC { public Object instance = null; public static void main(String[] args) { ReferenceCountingGC objectA = new ReferenceCountingGC(); ReferenceCountingGC objectB = new ReferenceCountingGC(); objectA.instance = objectB; objectB.instance = objectA; } }
正由於循環引用的存在,所以 Java 虛擬機不適用引用計數算法。github
經過 GC Roots 做爲起始點進行搜索,可以到達到的對象都是存活的,不可達的對象可被回收。web
Java 虛擬機使用該算法來判斷對象是否可被回收,在 Java 中 GC Roots 通常包含如下內容:算法
不管是經過引用計算算法判斷對象的引用數量,仍是經過可達性分析算法判斷對象的引用鏈是否可達,斷定對象是否可被回收都與引用有關。數組
Java 具備四種強度不一樣的引用類型。緩存
(一)強引用安全
被強引用關聯的對象不會被垃圾收集器回收。多線程
使用 new 一個新對象的方式來建立強引用。
Object obj = new Object();
(二)軟引用
被軟引用關聯的對象,只有在內存不夠的狀況下才會被回收。
使用 SoftReference 類來建立軟引用。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; // 使對象只被軟引用關聯
(三)弱引用
被弱引用關聯的對象必定會被垃圾收集器回收,也就是說它只能存活到下一次垃圾收集發生以前。
使用 WeakReference 類來實現弱引用。
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null;
WeakHashMap 的 Entry 繼承自 WeakReference,主要用來實現緩存。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
Tomcat 中的 ConcurrentCache 就使用了 WeakHashMap 來實現緩存功能。ConcurrentCache 採起的是分代緩存,常用的對象放入 eden 中,而不經常使用的對象放入 longterm。eden 使用 ConcurrentHashMap 實現,longterm 使用 WeakHashMap,保證了不常使用的對象容易被回收。
public final class ConcurrentCache<K, V> { private final int size; private final Map<K, V> eden; private final Map<K, V> longterm; public ConcurrentCache(int size) { this.size = size; this.eden = new ConcurrentHashMap<>(size); this.longterm = new WeakHashMap<>(size); } public V get(K k) { V v = this.eden.get(k); if (v == null) { v = this.longterm.get(k); if (v != null) this.eden.put(k, v); } return v; } public void put(K k, V v) { if (this.eden.size() >= size) { this.longterm.putAll(this.eden); this.eden.clear(); } this.eden.put(k, v); } }
(四)虛引用
又稱爲幽靈引用或者幻影引用。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用取得一個對象實例。
爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。
使用 PhantomReference 來實現虛引用。
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj); obj = null;
由於方法區主要存放永久代對象,而永久代對象的回收率比新生代差不少,所以在方法區上進行回收性價比不高。
主要是對常量池的回收和對類的卸載。
類的卸載條件不少,須要知足如下三個條件,而且知足了也不必定會被卸載:
能夠經過 -Xnoclassgc 參數來控制是否對類進行卸載。
在大量使用反射、動態代理、CGLib 等 ByteCode 框架、動態生成 JSP 以及 OSGi 這類頻繁自定義 ClassLoader 的場景都須要虛擬機具有類卸載功能,以保證不會出現內存溢出。
finalize() 相似 C++ 的析構函數,用來作關閉外部資源等工做。可是 try-finally 等方式能夠作的更好,而且該方法運行代價高昂,不肯定性大,沒法保證各個對象的調用順序,所以最好不要使用。
當一個對象可被回收時,若是須要執行該對象的 finalize() 方法,那麼就有可能經過在該方法中讓對象從新被引用,從而實現自救。
將須要回收的對象進行標記,而後清理掉被標記的對象。
不足:
讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
將內存劃分爲大小相等的兩塊,每次只使用其中一塊,當這一塊內存用完了就將還存活的對象複製到另外一塊上面,而後再把使用過的內存空間進行一次清理。
主要不足是隻使用了內存的一半。
如今的商業虛擬機都採用這種收集算法來回收新生代,可是並非將內存劃分爲大小相等的兩塊,而是分爲一塊較大的 Eden 空間和兩塊較小的 Survior 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活着的對象一次性複製到另外一塊 Survivor 空間上,最後清理 Eden 和使用過的那一塊 Survivor。HotSpot 虛擬機的 Eden 和 Survivor 的大小比例默認爲 8:1,保證了內存的利用率達到 90 %。若是每次回收有多於 10% 的對象存活,那麼一塊 Survivor 空間就不夠用了,此時須要依賴於老年代進行分配擔保,也就是借用老年代的空間存儲放不下的對象。
如今的商業虛擬機採用分代收集算法,它根據對象存活週期將內存劃分爲幾塊,不一樣塊採用適當的收集算法。
通常將 Java 堆分爲新生代和老年代。
以上是 HotSpot 虛擬機中的 7 個垃圾收集器,連線表示垃圾收集器能夠配合使用。
Serial 翻譯爲串行,能夠理解爲垃圾收集和用戶程序交替執行,這意味着在執行垃圾收集的時候須要停頓用戶程序。除了 CMS 和 G1 以外,其它收集器都是以串行的方式執行。CMS 和 G1 可使得垃圾收集和用戶程序同時執行,被稱爲併發執行。
它是單線程的收集器,只會使用一個線程進行垃圾收集工做。
它的優勢是簡單高效,對於單個 CPU 環境來講,因爲沒有線程交互的開銷,所以擁有最高的單線程收集效率。
它是 Client 模式下的默認新生代收集器,由於在用戶的桌面應用場景下,分配給虛擬機管理的內存通常來講不會很大。Serial 收集器收集幾十兆甚至一兩百兆的新生代停頓時間能夠控制在一百多毫秒之內,只要不是太頻繁,這點停頓是能夠接受的。
它是 Serial 收集器的多線程版本。
是 Server 模式下的虛擬機首選新生代收集器,除了性能緣由外,主要是由於除了 Serial 收集器,只有它能與 CMS 收集器配合工做。
默認開始的線程數量與 CPU 數量相同,可使用 -XX:ParallelGCThreads 參數來設置線程數。
與 ParNew 同樣是並行的多線程收集器。
其它收集器關注點是儘量縮短垃圾收集時用戶線程的停頓時間,而它的目標是達到一個可控制的吞吐量,它被稱爲「吞吐量優先」收集器。這裏的吞吐量指 CPU 用於運行用戶代碼的時間佔總時間的比值。
停頓時間越短就越適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗。而高吞吐量則能夠高效率地利用 CPU 時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。
提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間 -XX:MaxGCPauseMillis 參數以及直接設置吞吐量大小的 -XX:GCTimeRatio 參數(值爲大於 0 且小於 100 的整數)。縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,致使吞吐量降低。
還提供了一個參數 -XX:+UseAdaptiveSizePolicy,這是一個開關參數,打開參數後,就不須要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種方式稱爲 GC 自適應的調節策略(GC Ergonomics)。
是 Serial 收集器的老年代版本,也是給 Client 模式下的虛擬機使用。若是用在 Server 模式下,它有兩大用途:
是 Parallel Scavenge 收集器的老年代版本。
在注重吞吐量以及 CPU 資源敏感的場合,均可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。
CMS(Concurrent Mark Sweep),Mark Sweep 指的是標記 - 清除算法。
特色:併發收集、低停頓。併發指的是用戶線程和 GC 線程同時運行。
分爲如下四個流程:
在整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,不須要進行停頓。
具備如下缺點:
G1(Garbage-First),它是一款面向服務端應用的垃圾收集器,在多 CPU 和大內存的場景下有很好的性能。HotSpot 開發團隊賦予它的使命是將來能夠替換掉 CMS 收集器。
Java 堆被分爲新生代、老年代和永久代,其它收集器進行收集的範圍都是整個新生代或者老生代,而 G1 能夠直接對新生代和永久代一塊兒回收。
G1 把新生代和老年代劃分紅多個大小相等的獨立區域(Region),新生代和永久代再也不物理隔離。
經過引入 Region 的概念,從而將原來的一整塊內存空間劃分紅多個的小空間,使得每一個小空間能夠單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預測的停頓時間模型成爲可能。經過記錄每一個 Region 記錄垃圾回收時間以及回收所得到的空間(這兩個值是經過過去回收的經驗得到),並維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的 Region。
每一個 Region 都有一個 Remembered Set,用來記錄該 Region 對象的引用對象所在的 Region。經過使用 Remembered Set,在作可達性分析的時候就能夠避免全堆掃描。
若是不計算維護 Remembered Set 的操做,G1 收集器的運做大體可劃分爲如下幾個步驟:
具有以下特色:
更詳細內容請參考:Getting Started with the G1 Garbage Collector
對象的內存分配,也就是在堆上分配。主要分配在新生代的 Eden 區上,少數狀況下也可能直接分配在老年代中。
(一)對象優先在 Eden 分配
大多數狀況下,對象在新生代 Eden 區分配,當 Eden 區空間不夠時,發起 Minor GC。
(二)大對象直接進入老年代
大對象是指須要連續內存空間的對象,最典型的大對象是那種很長的字符串以及數組。
常常出現大對象會提早觸發垃圾收集以獲取足夠的連續空間分配給大對象。
-XX:PretenureSizeThreshold,大於此值的對象直接在老年代分配,避免在 Eden 區和 Survivor 區之間的大量內存複製。
(三)長期存活的對象進入老年代
爲對象定義年齡計數器,對象在 Eden 出生並通過 Minor GC 依然存活,將移動到 Survivor 中,年齡就增長 1 歲,增長到必定年齡則移動到老年代中。
-XX:MaxTenuringThreshold 用來定義年齡的閾值。
(四)動態對象年齡斷定
虛擬機並非永遠地要求對象的年齡必須達到 MaxTenuringThreshold 才能晉升老年代,若是在 Survivor 區中相同年齡全部對象大小的總和大於 Survivor 空間的一半,則年齡大於或等於該年齡的對象能夠直接進入老年代,無需等到 MaxTenuringThreshold 中要求的年齡。
(五)空間分配擔保
在發生 Minor GC 以前,虛擬機先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是條件成立的話,那麼 Minor GC 能夠確認是安全的;若是不成立的話虛擬機會查看 HandlePromotionFailure 設置值是否容許擔保失敗,若是容許那麼就會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於,將嘗試着進行一次 Minor GC,儘管此次 Minor GC 是有風險的;若是小於,或者 HandlePromotionFailure 設置不容許冒險,那這時也要改成進行一次 Full GC。
對於 Minor GC,其觸發條件很是簡單,當 Eden 區空間滿時,就將觸發一次 Minor GC。而 Full GC 則相對複雜,有如下條件:
(一)調用 System.gc()
此方法的調用是建議虛擬機進行 Full GC,雖然只是建議而非必定,但不少狀況下它會觸發 Full GC,從而增長 Full GC 的頻率,也即增長了間歇性停頓的次數。所以強烈建議能不使用此方法就不要使用,讓虛擬機本身去管理它的內存。可經過 -XX:DisableExplicitGC 來禁止 RMI 調用 System.gc()。
(二)老年代空間不足
老年代空間不足的常見場景爲前文所講的大對象直接進入老年代、長期存活的對象進入老年代等,當執行 Full GC 後空間仍然不足,則拋出 Java.lang.OutOfMemoryError。爲避免以上緣由引發的 Full GC,調優時應儘可能作到讓對象在 Minor GC 階段被回收、讓對象在新生代多存活一段時間以及不要建立過大的對象及數組。
(三)空間分配擔保失敗
使用複製算法的 Minor GC 須要老年代的內存空間做擔保,若是出現了 HandlePromotionFailure 擔保失敗,則會觸發 Full GC。
(四)JDK 1.7 及之前的永久代空間不足
在 JDK 1.7 及之前,HotSpot 虛擬機中的方法區是用永久代實現的,永久代中存放的爲一些 Class 的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,永久代可能會被佔滿,在未配置爲採用 CMS GC 的狀況下也會執行 Full GC。若是通過 Full GC 仍然回收不了,那麼虛擬機會拋出 java.lang.OutOfMemoryError,爲避免以上緣由引發的 Full GC,可採用的方法爲增大永久代空間或轉爲使用 CMS GC。
(五)Concurrent Mode Failure
執行 CMS GC 的過程當中同時有對象要放入老年代,而此時老年代空間不足(有時候「空間不足」是 CMS GC 時當前的浮動垃圾過多致使暫時性的空間不足觸發 Full GC),便會報 Concurrent Mode Failure 錯誤,並觸發 Full GC。
免費Java資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q