垃圾收集(Garbage Collection,GC),它的任務是解決如下 3 件問題:java
其中第一個問題很好回答,在 Java 中,GC 主要發生在 Java 堆和方法區中,對於後兩個問題,咱們將在以後的內容中進行討論,並介紹 HotSpot 的 7 個垃圾收集器。git
何時回收對象?固然是這個對象不再會被用到的時候回收。因此要想解決 「何時回收?」 這個問題,咱們要先能判斷一個對象何時何時真正的 「死」 掉了,判斷對象是否可用主要有如下兩種方法。github
objA.instance = objB; objB.instance = objA;
,objA 和 objB 都不會再被訪問後,它們仍然相互引用着對方,因此它們的引用計數器不爲 0,將永遠不能被判爲不可用。即使如此,一個對象也不是一旦被判爲不可達,就當即死去的,宣告一個的死亡須要通過兩次標記過程。算法
JDK 1.2 後,Java 中才有了後 3 種引用的實現。安全
Object obj = new Object()
這種,只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的對象。SoftReference
。WeakReference
。PhantomReference
。finalize()
方法的判斷;
finalize()
方法,或者 finalize()
方法已被執行過(finalize()
只被執行一次);finalize()
方法是對象逃脫死亡的最後一次機會,不過虛擬機不保證等待 finalize()
方法執行結束,也就是說,虛擬機只觸發 finalize()
方法的執行,若是這個方法要執行超久,那麼虛擬機並不等待它執行結束,因此最好不要用這個方法。finalize()
方法能作的,try-finally 都能作,因此忘了這個方法吧!永久代的 GC 主要回收:廢棄常量 和 無用的類。數據結構
算法描述:多線程
不足: 可用內存縮小爲原來的一半,適合GC事後只有少許對象存活的新生代。架構
節省內存的方法:併發
-XX:SurvivorRatio=8
表示 Eden 區大小 / 1 塊 Survivor 區大小 = 8
。經過前兩小節對於判斷對象生死和垃圾收集算法的介紹,咱們已經對虛擬機是進行 GC 的流程有了一個大體的瞭解。可是,在 HotSpot 虛擬機中,高效的實現這些算法也是一個須要考慮的問題。因此,接下來,咱們將研究一下 HotSpot 虛擬機究竟是如何高效的實現這些算法的,以及在實現中有哪些須要注意的問題。jvm
經過以前的分析,GC 算法的實現流程簡單的來講分爲如下兩步:
想要找到死掉的對象,咱們就要進行可達性分析,也就是從 GC Root 找到引用鏈的這個操做。
也就是說,進行可達性分析的第一步,就是要枚舉 GC Roots,這就須要虛擬機知道哪些地方存放着對象應用。若是每一次枚舉 GC Roots 都須要把整個棧上位置都遍歷一遍,那可就費時間了,畢竟並非全部位置都存放在引用呀。因此爲了提升 GC 的效率,HotSpot 使用了一種 OopMap 的數據結構,OopMap 記錄了棧上本地變量到堆上對象的引用關係,也就是說,GC 的時候就不用遍歷整個棧只遍歷每一個棧的 OopMap 就好了。
在 OopMap 的幫助下,HotSpot 能夠快速準確的完成 GC 枚舉了,不過,OopMap 也不是萬年不變的,它也是須要被更新的,當內存中的對象間的引用關係發生變化時,就須要改變 OopMap 中的相應內容。但是能致使引用關係發生變化的指令很是之多,若是咱們執行完一條指令就改下 OopMap,這 GC 成本實在過高了。
所以,HotSpot 採用了一種在 「安全點」 更新 OopMap 的方法,安全點的選取既不能讓 GC 等待的時間過長,也不能過於頻繁增長運行負擔,也就是說,咱們既要讓程序運行一段時間,又不能讓這個時間太長。咱們知道,JVM 中每條指令執行的是很快的,因此一個超級長的指令流也可能很快就執行完了,因此 真正會出現 「長時間執行」 的通常是指令的複用,例如:方法調用、循環跳轉、異常跳轉等,虛擬機通常會將這些地方設置爲安全點更新 OopMap 並判斷是否須要進行 GC 操做。
此外,在進行枚舉根節點的這個操做時,爲了保證準確性,咱們須要在一段時間內 「凍結」 整個應用,即 Stop The World(傳說中的 GC 停頓),由於若是在咱們分析可達性的過程當中,對象的引用關係還在變來變去,那是不可能獲得正確的分析結果的。即使是在號稱幾乎不會發生停頓的 CMS 垃圾收集器中,枚舉根節點時也是必需要停頓的。這裏就涉及到了一個問題:
咱們讓全部線程跑到最近的安全點再停頓下來進行 GC 操做呢?
主要有如下兩種方式:
除此安全點以外,還有一個叫作 「安全區域」 的東西,一個一直在執行的線程能夠本身 「走」 到安全點去,但是一個處於 Sleep 或者 Blocked 狀態的線程是沒辦法本身到達安全點中斷本身的,咱們總不能讓 GC 操做一直等着這些個 」不執行「 的線程從新被分配資源吧。對於這種狀況,咱們要依靠安全區域來解決。
安全區域是指在一段代碼片斷之中,引用關係不會發生變化,所以在這個區域中的任意位置開始 GC 都是安全的。
當線程執行到安全區域時,它會把本身標識爲 Safe Region,這樣 JVM 發起 GC 時是不會理會這個線程的。當這個線程要離開安全區域時,它會檢查系統是否在 GC 中,若是不在,它就繼續執行,若是在,它就等 GC 結束再繼續執行。
本小節咱們主要講述 HotSpot 虛擬機是如何發起內存回收的,也就是如何找到死掉的對象,至於如何清掉這些個對象,HotSpot 將其交給了一堆叫作 」GC 收集器「 的東西,這東西又有好多種,不一樣的 GC 收集器的處理方式不一樣,適用的場景也不一樣,咱們將在下一小節進行詳細講述。
垃圾收集器就是內存回收操做的具體實現,HotSpot 裏足足有 7 種,爲啥要弄這麼多,由於它們各有各的適用場景。有的屬於新生代收集器,有的屬於老年代收集器,因此通常是搭配使用的(除了萬能的 G1)。關於它們的簡單介紹以及分類請見下圖。
Serial 收集器是虛擬機在 Client 模式下的默認新生代收集器,它的優點是簡單高效,在單 CPU 模式下很牛。
ParNew 收集器就是 Serial 收集器的多線程版本,雖然除此以外沒什麼創新之處,但它倒是許多運行在 Server 模式下的虛擬機中的首選新生代收集器,由於除了 Serial 收集器外,只有它能和 CMS 收集器搭配使用。
首先,這倆貨確定是要搭配使用的,不只僅如此,它倆還賊特別,它們的關注點與其餘收集器不一樣,其餘收集器關注於盡量縮短垃圾收集時用戶線程的停頓時間,而 Parallel Scavenge 收集器的目的是達到一個可控的吞吐量。
吞吐量 = 運行用戶代碼時間 / ( 運行用戶代碼時間 + 垃圾收集時間 )
所以,Parallel Scavenge 收集器無論是新生代仍是老年代都是多個線程同時進行垃圾收集,十分適合於應用在注重吞吐量以及 CPU 資源敏感的場合。
可調節的虛擬機參數:
-XX:MaxGCPauseMillis
:最大 GC 停頓的秒數;-XX:GCTimeRatio
:吞吐量大小,一個 0 ~ 100 的數,最大 GC 時間佔總時間的比率 = 1 / (GCTimeRatio + 1)
;-XX:+UseAdaptiveSizePolicy
:一個開關參數,打開後就無需手工指定 -Xmn
,-XX:SurvivorRatio
等參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,自行調整。參數設置:
-XX:+UseCMSCompactAtFullCollection
:在 CMS 要進行 Full GC 時進行內存碎片整理(默認開啓)-XX:CMSFullGCsBeforeCompaction
:在多少次 Full GC 後進行一次空間整理(默認是 0,即每一次 Full GC 後都進行一次空間整理)關於 CMS 使用 標記 - 清除 算法的一點思考:
以前對於 CMS 爲何要採用 標記 - 清除 算法十分的不理解,既然已經有了看起來更高級的 標記 - 整理 算法,那 CMS 爲何不用呢?最近想了想,感受多是這個緣由,不過也不是很肯定,只是我的的一種猜想。
標記 - 整理 會將全部存活對象向一端移動,而後直接清理掉邊界之外的內存。這就意味着須要一個指針來維護這個分隔存活對象和無用空間的點,而咱們知道 CMS 是併發清理的,雖然咱們啓動了多個線程進行垃圾回收,不過若是使用 標記 - 整理 算法,爲了保證線程安全,在整理時要對那個分隔指針加鎖,保證同一時刻只有一個線程能修改它,加鎖的這一過程至關於將並行的清理過程變成了串行的,也就失去了並行清理的意義了。
因此,CMS 採用了 標記 - 清除 算法。
原文:Java架構筆記
免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q