Tip:內容爲對《深刻理解Java虛擬機》(周志明 著)第三章內容的總結和筆記。這是第一次拜讀時讀到的一些重點,作個分享,也爲後面再次閱讀和實踐作保障。java
程序計數器、虛擬機棧、本地方法棧三個區域跟隨線程的生命週期,棧中的棧幀隨方法的進出而有序的進行出棧和入棧,每個棧幀分配多少內存基本上是在類節後肯定下來時就已知的。算法
Java堆和方法區只有在程序運行時才能肯定內存的使用狀況,垃圾回收器所關注的主要就是這部份內存。數組
在堆中,尤爲是在新生代中,常規應用進行一次垃圾收集通常能夠回收70%~95%的空間,而永久代的垃圾收集效率遠低於此。安全
給對象添加一個引用計數器,每當有地方引用它時,計數器就加一;當引用失效時,計數器就減一;任什麼時候刻計數器爲0的對象就是不可能被再被使用的。數據結構
引用計數算法實現簡單,斷定效率也很高;可是它很難解決對象間相互循環引用的問題。JVM 沒有選用這種方法管理內存。多線程
算法的基本思路就是經過一系列的稱爲「 GC ROOT 」的對象做爲起始點,從這些節點開始向下搜索。從圖論的角度看,當一個對象到 GC ROOT 不可達的時候這個對象就是不可用的;反之則是可用的。併發
在 Java 中,能夠做爲 GC ROOT 的對象包括如下4種:jvm
虛擬機棧(棧幀中的本地變量表)中引用的對象;性能
方法區中類靜態屬性引用的對象;spa
方法區中常量引用的對象;
本地方法棧中 JNI(即 Native 方法)引用的對象。
在 JDK1.2 後,Java 對引用概念擴充,分爲強引用、軟引用、弱引用、虛引用。強度漸弱。
強引用
就是值在程序代碼之中廣泛存在的,相似 Object obj = new Object()
這類的引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象。
軟引用
它關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍內進行第二次回收。提供 SoftReference 類來實現軟引用。
弱引用
強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。提供 WeakReference 類來實現軟引用。
虛引用
一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來去的一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。提供 PhantomReference 類來實現軟引用。
即便在可達性分析算法中不可達的對象,也並非當即就會被銷燬,它至少要經歷兩次標記過程:
一個對象在分析後發現是不可達的,它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize() 方法。當對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經被調用過,虛擬機將認爲它不必再執行。即任何一個對象的 finalize() 方法都只會被系統自動調用一次。
若是對象斷定認爲有必要執行 finalize() 方法,那麼這個對象會進入 F-Queue 隊列中,並在稍後由一個虛擬機自動創建的、低優先級的 Finalize 線程去執行它。這裏的「執行」是指虛擬機會觸發這個方法,可是不必定會等它結束(防止阻塞)。而後 GC 會將 F-Queue 中的對象進行第二次標記,若是在 finalize 方法中與 GC ROOT 創建關聯,那麼它將被移除「即將回收」的集合。
永久代中的垃圾收集主要回收兩部分的內容:廢棄常量和無用的類。
廢棄常量的回收和堆中對象的回首方法相似。
類須要知足下面3個條件纔算是「無用的類」。知足這些條件只是能夠被回收,是否確定回收還有虛擬機的一些參數控制。
該類的全部實例都已經被回收;
加載該類的 ClassLoader 已經被回收;
該類對應的 java.lang.Class 對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。
思想:算法分爲「標記」和「清除」兩個階段。
首先標記出全部須要回收的對象;
標記完後贊成回收全部被標記的對象。
這種算法有兩個不足:
一是效率問題,標記和清除兩個過程效率都不過高;
二是空間問題,空間會碎片化。
複製算法是爲了解決效率問題,也解決了內存碎片的問題。
思想:
它將可用的內存分爲相等的兩塊;
每次只用一塊;
GC 時將還存活的對象複製到另外一塊上面,而後全面清理使用過的內存。
代價是將內存縮小爲原來的一半。
思想:
標記過程跟「標記-清除」算法同樣;
而後讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存。
當前商業虛擬機的垃圾收集都採用「分代收集」算法。
思想:
根據對象存活週期的不一樣將內存劃分爲幾塊。(通常是把 Java 堆分爲新生代和老年代)
新生代中,採用複製算法回收。由於新生代中對象98%是很短暫的。因此沒必要按照1:1劃份內存,而是將內存分爲一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一個 Survivor 。回收時將存活的對象複製到另外一塊 Survivor 中,而後清理 Eden 和使用過的 Survivor 空間。HotSpot 中 Eden 和 Survivor 比例爲8:1。當 Survivior 空間不夠時,須要依賴其它內存(老年代)進行分配擔保。
老年代中由於對象存活率高、沒有額外的空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」算法進行回收。
可做爲 GC ROOT 的節點主要在全局性的引用(eg.常量、類靜態屬性)和執行上下文(eg.棧幀中的本地變量表)。
在可達性分析時,執行系統須要在一個一致性的快照中(一致性的意思就是說在分析期間執行系統中對象引用關係不能夠還在不斷變化,不然分析準確性沒法保證)。這是 GC 進行時必須停頓全部 JAVA 執行線程的一個重要緣由。
準確式 GC:虛擬機能夠知道內存中某個位置的數據具體是什麼類型。這樣在 GC 的時候虛擬機能準確的判斷堆上的數據是否還可能被使用。
在 HotSpot 中一組 OopMap 的數據結構來記錄哪些地方存放着對象引用。(普通對象指針 Ordinary Object Pointer)
可能致使引用關係變化(Oop 內容變化)的指令不少,爲每條指令生成 OopMap 是不現實的。
HotSpot 只有在安全點才能暫停。
安全點的選定基本上是以「是否具備讓程序長時間執行的特徵」爲標準進行選定的。
安全點的另外一個要處理的問題是:如何在 GC 發生時讓全部的線程都到最近的安全點停下來。這裏有兩種方案:
搶斷式中斷:不須要線程主動配合,GC 發生時,先中斷全部線程,發現有線程不在安全點上再恢復它,讓它運行至安全點。
主動式中斷:當須要 GC 時,僅僅在安全點設置一個標誌,各線程執行時主動去輪詢這個標誌,發現爲真就中斷掛起。
當線程處於 Sleep 狀態或者 Blocked 狀態時,它沒法響應 JVM 的中斷請求,此時須要安全區域來解決。
安全區域就是指在一段代碼片斷中,引用關係不會發生變化,在這個區域中的任何地方開始 GC 都是安全的。
當線程要離開安全區域時,她要檢查系統是否已經完成了根節點枚舉,完成才能離開。
收集器採用複製算法;
這個收集器是一個單線程的收集器;
它只會使用一個 CPU一個線程去完成垃圾收集工做;
它在進行垃圾收集時,必須暫停其它全部的工做線程,直到它收集結束!
收集器採用複製算法;
ParNew 收集器就是 Serial 收集器的多線程版本;
目前只有它能與 CMS 收集器配合工做;
HotSpot 虛擬機中第一款真正意義上的併發收集器;
Parallel Scavenge 收集器是一個新生代收集器,採用複製算法,也是一個並行的多線程收集器;
它不一樣於其它收集器的特色是:
跟其它收集器關注點不同,CMS 等收集器目標是儘量縮短垃圾收集時用戶線程的停頓時間;PS 收集器的目標是吞吐量優先(吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間));
GC 自適應調整策略。虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整 GC 比率參數以提供最合適的停頓時間或最大的吞吐量。
Serial Old 是 Serial 的老年代版本;
它是一個單線程收集器;
採用「標記-整理」算法;
在 Server 模式下,它還有兩大用途:
在 JDK1.5 以前與 Parallel Scavenge 收集器配合使用;
做爲 CMS 收集器的後備預案,在併發收集發生 CMF(Concurrent Mode Failure)時使用。
Parallel Old 是 Parallel Scavenge收集器的老年代版本;
是一個多線程收集器;
採用「標記-整理」算法;
在注重吞吐量以及 CPU 資源敏感的場合,能夠考慮 Parallel Scavenge 加 Parallel Old 收集器。
CMS 收集器的目標是獲取最短回收停頓時間,適合重視服務的響應速度的場合。
採用「標記-清除」算法;
CMS 收集器的運行過程;
初始標記(須要 Stop The World):標記 GC ROOT 直接關聯到的對象。
併發標記:進行 GC ROOT 的 Tracing 過程,便可達性分析。
從新標記(須要 Stop The World):修正併發標記階段因用戶繼續運行致使的標記的變更。時間通常比初始標記長,比並發標記短。
併發清除:根據標記清除內存。
整個過程當中耗時最長的併發標記和併發清除過程收集線程均可以和用戶線程一塊兒工做。
CMS 收集器也有三個明顯的缺點:
CMS 收集器對 CPU 資源很是敏感。
雖然不會致使用戶線程停頓,可是會佔用一部分 CPU 資源而致使程序變慢。回收線程數是 (CPU 數量 + 3) / 4。
CMS 收集器沒法處理浮動垃圾(併發清理階段產生的垃圾)。
浮動垃圾要留到下次 GC 時再進行清理。由於並行清理時用戶線程還在運行,因此要預留一部分老年代空間提供線程使用(JDK1.6 爲92%時開始)。若是預留沒法知足要求,會出現「Concurrent Mode Failure」錯誤,此時須要臨時啓用 Serial Old 收集器來進行老年代垃圾收集。
CMS 收集器會產生大量空間碎片。
G1 收集器是一款面向服務端應用的垃圾收集器;
與其它收集器相比有如下特色:
並行與併發
能充分利用多 CPU、多核環境下的硬件優點。
分代收集
G1 收集器獨立管理整個 GC 堆,但依然有分代的概念,用不一樣的方式處理新老對象。
空間整理
G1 總體上是基於「標記-整理」算法的,從局部(兩個 Region 之間)看是基於「複製」算法的,都不會產生內存碎片。
可預測的停頓
G1 能夠創建可預測的停頓時間模型。
實現思想:
它將整個 JAVA 內存堆劃分爲多個大小相等的獨立區域(Region);
G1 跟蹤各個 Region 裏面的垃圾堆積的價值大小,優先回收價值最大的 Region。
在 G1 收集器的 Region 之間的對象引用以及其它收集器中的新生代與老年代之間的對象引用,虛擬機都是使用 Remembered Set 來避免全堆掃描的。G1 中每一個 Region 都有一個與之對應的 Remembered Set .
運行過程:
初始標記:標記 GC ROOT 直接關聯到的對象,而且修改 TAMS(Next Top at Mark Start)的值,以便下次用戶程序運行時,能在正確的可用的 Region 中建立對象。
併發標記:進行可達性分析。
最終標記:修正併發標記階段因用戶繼續運行致使的標記的變更。更新 Remembered Set。
篩選回收:對各個 Region 回收價值排序,而後根據用戶指望的 GC 停頓時間來制定回收計劃。
大多數狀況下,對象在新生代 Eden 區中分配。當 Eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC(新生代 GC)。
典型的大對象就是那種很長的字符串以及數組。
虛擬機提供一個 -XX:PretenureSizeThreshold 參數,令大於這個設置值的對象直接在老年代分配。目的是避免在 Eden 區及兩個 Survivor 區之間發生大量的內存複製。
虛擬機給每一個對象設置一個對象年齡(AGE)計數器;
每通過一次 Minor GC,年齡增長一歲;當到必定歲數(默認15)後進入老年代。
爲了更好地適應不一樣程序的內存情況,若是在Survivor 空間中相同年齡全部對象大小綜合大於 Survivor 空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代。
在發生 Minor GC 以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是這個條件成立,那麼 Minor GC 能夠確保是安全的。
若是條件不成立,虛擬機會查看是否容許擔保失敗。若是容許,會檢查老年代最大可用的連續空間是否大於歷次晉升帶老年代對象的平均大小,大於則會進行一次 Minor GC;小於或者不容許蛋白失敗則改成進行一次 Full GC。
JDK1.6 Update 24後強制容許擔保失敗,HandlePromotionFailure 參數不起做用了。