深入理解Java虛擬機器——垃圾收集器與記憶體分配策略(讀書筆記)
判斷物件是否存活
1、引用計數法
給物件新增一個引用計數器,每當有一個地方引用它時,計數器值加1,當引用失效時,計數器值減1, 任何時刻計數器為0的物件就是不可能再被使用的。
缺點:不能解決物件之間迴圈引用的問題
2、根搜尋演算法(GC Roots Tracing)
通過一系列名為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。
可作為GC Roots物件:
(1)虛擬機器棧中引用的物件
(2)本地方法棧中引用的物件
(3)方法區中類靜態屬性引用的物件
(4)方法區中常量引用的物件
引用的分類:強軟弱虛(強度依次減弱)
1、強引用:最常見的,如Object obj = new Object();,只要強引用還在,垃圾回收器永遠不會回收掉被引用的物件。
2、軟引用(SoftReference):描述一些還有用,但並非必需的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍內並進行第二次回收,如果還是沒有足夠的記憶體,才會丟擲異常。
3、弱引用(WeakReference):被弱引用關聯的物件只能生存到下一次垃圾收集發生。
4、虛引用(PhantomReference):一個物件是否有虛引用關聯,完全不會對其生存時間產生影響,也無法通過虛引用來取得一個物件例項。作用:物件被回收時收到一個系統通知。
finalize()方法
當物件要被刪除時,並且物件實現了finalize()方法,還沒有被呼叫過,那麼,這個物件會被放在F-Queue佇列,由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行,但是,虛擬機器只負責觸發,沒有承諾會等待它結束,所以最終能否存活,不確定性很大。
回收方法區
永久代垃圾回收:廢棄的常量、無用的類
無用的類(滿足所有條件):
1、該類所有的例項都已經被回收
2、載入該類的ClassLoader已經被回收
3、該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
ps:在大量使用反射、動態代理、CGLib等bytecode框架的場景,以及動態生成JSP和OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機器具備類解除安裝的功能,以保證永久帶不會溢位。
垃圾收集演算法
1、標記——清除演算法
缺點:
標記和清除的效率低
標記清除後會產生大量不連續的記憶體碎片,當需要給較大物件分配記憶體時,提前觸發垃圾回收動作。
2、複製演算法
將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊記憶體用完了,就將還存活的物件複製到另一塊上面,然後再把已使用過的記憶體清理掉。
缺點:犧牲掉一半記憶體
3、標記——整理演算法
標記後,將所有存活的物件向一段移動,然後直接清理掉端邊界以外的記憶體。
4、分代收集演算法
根據物件的存活週期的不同將記憶體劃分為幾塊,一般是分為新生代和老年代,然後根據各個年代的特點採用合適的收集演算法。新生代一般採用標記複製,老年代一般採用標記清理、標記整理演算法。
垃圾收集器
1、Serial收集器:
單執行緒
STW(工作時必需暫停其他工作執行緒)
虛擬機器在Client模式下預設的新生代收集器
優點:簡單高效
2、PerNew收集器:
Serial的多執行緒版本
STW
虛擬機器在Server模式下預設新生代收集器
預設開啟的收集器的個數和CPU相同
3、Parallel Scavenge收集器
目標是達到一個可控制的吞吐量
吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)
高吞吐量即最高效率的利用CPU時間,儘快的完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務
設定最大垃圾收集停頓時間:-XX:MaxGCPauseMillis
設定吞吐量大小:-XX:GCTimeRatio
-XX:+UseAdaptiveSizePolicy:開關引數,開啟後就不需要手動設定新生代的大小、各分割槽比例、晉升老年代年齡了,JVM會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數以提供最適合的停頓時間或最大吞吐量。
4、Serial Old收集器
Serial的老年代版本
標記整理演算法
CMS收集器的替補
5、Paralle Old收集器
Parallel Scavenge老年代版本
標記整理演算法
6、CMS收集器
以獲得最短停頓時間為目標
標記清除演算法
步驟:
初始標記:
STW
標記GC Roots能直接關聯到的物件
併發標記:
GC Roots Tracing
重新標記:
STW
修改併發標記期間,因使用者程序繼續運作而導致的產生變動的那一部分物件
併發清除
缺點:
(1)對CPU資源非常敏感,面向併發設計的程式都這樣
(2)無法處理浮動垃圾,併發清理的過程中會有新的垃圾產生,只能下次處理掉,稱為浮動垃圾,由於還需要留一部分空間給使用者執行緒,所以不能等到快滿了才收集,預設是68%,當預留空間不夠,會出現Concurrent Mode Failure,導致Full GC,這時,會啟用Serial Old收集器。
(3)產生大量空間碎片,引數-XX:+UseCMSCompactAtFullCollection,在Full GC之後進行碎片整理。
7、G1收集器
G1將整個Java堆劃分為多個大小固定的獨立區域(Region),並且跟蹤這些區域裡面的垃圾堆積程度,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收垃圾最多的區域(Garbage First)。保證了G1在有限的時間內可以獲得最高的收集效率。
標記整理演算法
非常精準的控制停頓,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不超過N毫秒
記憶體分配
1、大多數情況,物件在新生代Eden區中分配,當空間不足,JVM發起Minor GC
2、-XX:+PrintGCDetails:JVM在發生垃圾收集行為時列印日誌
3、GC
Minor GC:發生在新生代的垃圾收集動作,因為物件大多具有朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度比較快。
Major GC/Full GC:發生在老年代,出現Major GC,經常會伴隨至少一次的Minor GC,速度比Minor GC慢10倍以上。
4、大物件
大物件需要大量連續的記憶體空間,例如:很長的字串/陣列,
-XX:PretenureSizeThreshold:大於這個引數值的物件直接在老年代中分配(只對Serial、ParNew有效)
5、長期存活的物件進入老年代
JVM給每個物件定義一個物件年齡的計數器,經歷過一次Minor GC,加一,當年齡增加到一定程度(預設為15),就會被晉升到老年代。
-XX:MaxTenuringThreshold:設定晉升老年代的閾值
6、動態物件年齡判定
當Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於等於該年齡的物件可以直接進入老年代。
7、空間分配擔保
發生Minor GC時,JVM會檢測之前每次晉升到老年代的平均大小是否大於目前老年代剩餘空間,如果大於,則直接改為Full GC,如果小於,則檢視HandlePromotionFailure是否允許擔保失敗,如果允許,則只會進行Minor GC,否則,Full GC。