聲明:java
本文由Yasin Shaw原創,首發於我的網站yasinshaw.com和公衆號"xy的技術圈"。算法
若是須要轉載請聯繫我(微信:yasinshaw)並在文章開頭顯著的地方註明出處。數組
關注公衆號便可獲取學習資源或加入技術交流羣。緩存
在JDK 1.2 以後,對引用的概念進行了擴充,把引用分爲強引用、軟引用、弱引用、虛引用。安全
強引用就不說了,咱們平時用得最多的就是強引用。微信
軟引用通常用於圖片緩存、網頁緩存或其它形式的緩存框架中。「內存緩存」中的圖片是以這種引用保存,使得 JVM 在發生 OOM 以前,能夠回收這部分緩存。多線程
Browser prev = new Browser(); // 獲取頁面進行瀏覽
SoftReference sr = new SoftReference(prev); // 瀏覽完畢後置爲軟引用
if(sr.get() != null) {
rev = (Browser) sr.get(); // 尚未被回收器回收,直接獲取
} else {
prev = new Browser(); // 因爲內存吃緊,因此對軟引用的對象回收了
sr = new SoftReference(prev); // 從新構建
}
複製代碼
JDK中的WeakHashMap
和ThreadLocal
使用了弱引用。併發
設計WeakHashMap類是爲了解決一個有趣的問題:若是有一個值,對應的鍵已經再也不使用了,將會出現什麼狀況呢?框架
假定對某個鍵的最後一次引用已經消亡,再也不有任何途徑引用這個值的對象了。可是,因爲在程序中的任何部分沒有再出現這個鍵,因此,這個鍵值對沒法從Map中刪除。學習
WeakHashMap 使用弱引用(weak references) 保存鍵。 WeakReference對象將引用保存到另一個對象中,在這裏,就是散列鍵。對於這種類型的對象,垃圾回收器用一種特有的方式進行處理。
一般,若是垃圾回收器發現某個特定的對象已經沒有他人引用了,就將其回收。然而,若是某個對象只能由 WeakReference引用,垃圾回收器仍然回收它,但要將引用這個對象的弱引用放人隊列中。
WeakHashMap將週期性地檢查隊列,以便找出新添加的弱引用。一個弱引用進人隊列意味着這個鍵再也不被他人使用,而且已經被收集起來。因而,WeakHashMap將刪除對應的鍵值對。
JDK中直接內存的回收就用到虛引用,因爲JVM自動內存管理的範圍是堆內存,而直接內存是在堆內存以外(實際上是內存映射文件),因此直接內存的分配和回收都是由Unsafe類去操做。
Java在申請一塊直接內存以後,會在堆內存分配一個對象保存這個堆外內存的引用,這個對象被垃圾收集器管理,一旦這個對象被回收,相應的用戶線程會收到通知並對直接內存進行清理工做。
有的,咱們可使用最近最少使用算法(LRU)。其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。最多見的實現是使用一個鏈表保存緩存數據,詳細算法實現以下:
Redis的鍵過時策略就使用了LRU算法。
指的是程序在某個點或者某個區域是GC安全的。
對於Safe Point,另外一個須要考慮的問題是如何在GC發生時讓全部線程(這裏不包括執行JNI調用的線程)都「跑」到最近的安全點上再停頓下來。
這裏有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension),其中搶先式中斷不須要線程的執行代碼主動去配合,在GC發生時,首先把全部線程所有中斷, 若是發現有線程中斷的地方不在安全點上,就恢復線程,讓它「跑」到安全點上。如今幾乎沒有虛擬機實現採用搶先式中斷來暫停線程從而響應GC事件。
而主動式中斷的思想是當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單地設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就本身中斷掛起。輪詢標誌的地方和安全點是重合的,另外再加上建立對象須要分配內存的地方。
使用Safe Point彷佛已經完美地解決了如何進入GC的問題,但實際狀況卻並不必定。Safe Point機制保證了程序執行時,在不太長的時間內就會遇到可進入GC的Safe Point。
可是,程序「不執行」的時候呢?所謂的程序不執行就是沒有分配CPU時間,典型的例子就是線程處於Sleep狀態或者Blocked狀態,這時候線程沒法響應JVM的中斷請求,「走」到安全的地方去中斷掛起,JVM也顯然不太可能等待線程從新被分配CPU時間。對於這種狀況,就須要安全區域(Safe Region)來解決。
安全區域是指在一段代碼片斷之中,引用關係不會發生變化。在這個區域中的任意地方開始GC都是安全的。咱們也能夠把Safe Region看作是被擴展了的Safe Point。
在線程執行到Safe Region中的代碼時,首先標識本身已經進入了Safe Region,那樣,當在這段時間裏JVM要發起GC時,就不用管標識本身爲Safe Region狀態的線程了。在線程要離開Safe Region時,它要檢查系統是否已經完成了根節點枚舉(或者是整個GC過程),若是完成了,那線程就繼續執行,不然它就必須等待直到收到能夠安全離開Safe Region的信號爲止。
新生代: Serial:單線程,新生代; ParNew: 多線程,新生代; Parallel Scavenge:多線程,新生代,關注吞吐量,容許較長的STW(Stop the world)換取吞吐量最大化; 老年代: Serial Old: 單線程,Serial的老年代版本; Parallel Old:多線程,Parallel Scavenge的老年代版本,關注吞吐量; CMS:多線程,標記-清除算法,關注停頓時間,能夠與Serial和ParNew配合。 其它: G1:同時負責新生代和老年代,是目前一段時間主流的垃圾收集器(JDK 9 到 11 的默認垃圾收集器)。 ZGC:在大堆下也能夠控制STW時間極短(幾毫秒內),在JDK 11 爲實驗階段。
這裏只討論Server模式.
在JDK7,默認是Parallel Scavenge + Serial Old。
在JDK 8 及JDK 7u4以後的版本,默認是Parallel Scavenge + Parallel Old。
在JDK 9 到JDK 11,默認是G1
四個階段:
耗時排序:併發清除> 併發標記> 從新標記 > 初始標記
CMS在老年代達到閾值(默認92%,能夠經過參數調整閾值)的時候,會進行Full GC。
若是在「併發清除」階段,因爲程序繼續運行,產生了過多的垃圾,預留的內存沒法知足程序須要,就會出現Concurrent Mode Failure。
若是出現了CMF,這時虛擬機將啓動後備預案: 臨時啓用Serial Old收集器來從新進行Full GC,這樣停頓時間就很長了。
G1一直在持續不斷地改進中,G1設計的目的是替換CMS,它最主要的優勢是創建了「可預測的停頓模型」,它能夠儘可能去知足用戶指望的停頓時間。
同時,G1解決了CMS碎片化太多的問題。JVM系列下一篇文章咱們將重點討論G1。