jvm筆記-02-垃圾收集器與內存分配策略
[TOC]
對象的生存與死亡
如何判定一個對象的生與死
引用計數法
Reference Counting
給對象中添加一個引用計數器,被引用加1,引用失效減1,計數器為0則對象可以被回收。
缺點:很難解決對象之間互相循環引用的問題。可達性分析法
通過一系列Reachability Analysis
GC Roots
的對象作為起點,向下搜索,遍歷路徑稱為引用鏈。當一個對象到GC Roots
不可達時,證明此對象是不可用的。
GC Roots
對象包括:- 虛擬機棧中引用的對象
- 方法區中靜態常量引用對象
- 方法區中常量引用對象
- 本地方法棧中引用的對象
死亡面前,各種引用不平等
這樣的一類對象:當內存空間足夠是,保存在內存之中。若內存空間在垃圾回收之後仍不足,則可以拋棄這些對象。應用場景如系統的緩存功能。
強引用
普遍存在的引用,只要引用關系存在,就不會被回收。
軟引用
還有用但非必須的對象,系統將要發生內存溢出之前,將會把這些對象列入回收範圍之內。
SoftReference
類實現。弱引用
描述非必須對象,比軟引用更弱一些,被弱引用關聯對象只能生存到下一次垃圾收集之前。
WeakReference
類實現。虛引用
幽靈引用或幻影引用,不會對對象生存時間造成影響,也無法通過虛引用獲取對象實例。
為對象設置虛引用關聯唯一目的就是能在這個對象被回收時收到一個系統通知。PhantomReference
類實現。
死亡,不過如此 finalize()方法
可達性分析算法中不可達的對象,到被回收之間至少經歷兩次標記過程:
對象在進行可達性分析之後發現沒有與
GC Roots
的引用鏈,會被第一次標記並篩選。
篩選的條件是對象是否有必要去執行finalize()
方法。
當對象沒有覆蓋該方法,或該方法已經被虛擬機調用過,則沒必要執行。有必要執行
finalize()
方法的對象,被放入F-Queue
隊列中觸發該方法。
這是對象自我拯救的最後一次機會:將自己賦值給某個類變量或成員變量。
稍後 GC 將對F-Queue
中對象進行第二次標記,正式宣告對象死亡。
有點像尋夢環遊記裏的,如果活著的人沒人記得你,你就會在亡靈世界裏終極死亡。
回收方法區(永久代)
永久代垃圾回收主要是兩部分內容:廢棄常量與無用的類。
判斷常量廢棄的條件:
沒有任何對象引用常量池中的常量,如字面量、接口、方法、字段的符號引用,垃圾回收時必要時會被回收。
判斷類無用的條件:
1. 該類的所有實例已被回收
2. 加載該類的 ClassLoader 已被回收
3. 該類對應的 java.lang.Class對象沒有被引用,即無法通過反射訪問該類方法。
回收無用的類型,使用 -Xnoclassgc
控制。
在大量使用反射、動態代理、CGLib等字節碼框架、動態生成JSP以及OSGI頻繁自定義ClassLoader的場景,需要虛擬機具備類卸載功能。
垃圾收集算法
標記-清除算法 Mark-Sweep
原理:
- 標記階段:對於不可達的對象進行標記。
- 清除階段:對兩次標記的對象進行回收。
缺點:
- 效率問題:標記和清除的效率都不高
- 空間問題:會產生大亮不連續的空間碎片,須分配較大對象時,會因為無法找到足夠大的內存而提前觸發垃圾收集動作。
復制算法 Copying
原理:
將內存劃分為兩塊,每次使用其中一塊,使用完時將其中存活對象復制到另一塊,將已經使用的內測空間清理掉。
優缺點:
實現簡單,運行高效。
可用內存空間變小,且只適用於年輕代。
內存分配:
新生代分為較大的 Eden
空間和兩塊較小的 Survivor
空間。每次使用 Eden
和 其中一塊 Survivor
空間。
HotSpot虛擬機默認 Eden
:Survivor
= 8:1
當回收時 Survivor
空間不足,需要老年代進行分配擔保。
標記整理算法 Mark-Compact
原理:
- 標記同
標記-清除算法
中的標記階段。 - 整理:讓所有存活對象向一端移動,然後清理掉邊界以外的內存。
優點:
- 解決
復制算法
在對象存活率較高時復制操作效率低問題。適用於老年代。 - 不會浪費內存空間。
分代收集算法 Generational Collection
根據對象存活周期不同將內存劃分為新生代和老年代。
新生代對象存活少,選用 復制算法。
老年代對象存活率高、沒有額外空間分配擔保,選用 標記-清除算法 或 標記整理算法
hotspot的算法實現
枚舉根節點 GC Roots
原理:
作為GC Roots
的節點主要在全局性的引用(常量和靜態屬性)和執行上下文(棧幀的本地變量表)中。
難點:
- 方法區可能較大,逐個檢查引用效率低。
- GC停頓,整個執行系統中引用關系不斷變化會影響分析結果準確性。
Stop The Word
解決:
- 使用一組稱為
OopMap
的數據結構,直接得知執行上下文和全局引用的位置。 - 使用
OopMap
可快速完成GC Roots
枚舉,但前提是枚舉時引用關系不會變化。這個使用安全點Safepoint
解決。
安全點 Safepoint
問題:
GC Roots
枚舉時可能引用關系變化,或者說,導致 OopMap
內容變化的指令非常多,若為每條指令生成 OopMap
,將會需要大量額外空間。
解決:
只在特定位置記錄 OopMap
信息,這些位置即安全點。
程序執行到安全點才可以GC。
安全點的選定:
是以程序是否具有讓程序長時間執行的特征為標準選定的,特征是指令序列復用,如方法調用、循環跳轉、異常跳轉等。
意思是在你經常路過的地方堵著你。
如何在GC發生時讓所有線程都泡到安全點上停頓下來?
搶先式中斷
先把所有線程全部中斷,如果有線程中斷的地方不在安全點上,就恢復線程讓它跑到安全點上。
主動式中斷
當GC需要中斷線程時,見設置一個標識,個線程執行時主動輪詢該標識,發現為真時就自己中斷掛起。輪詢標誌的地方與安全點重合。
安全區域 SafeRegion
Safepoint
的問題:
若程序不執行如處於 Sleep
或 Blocked
狀態的線程,無法響應JVM的中斷請求。
解決:
安全區域指在一端代碼片段中,引用關系不會發生變化。可以看作擴展的 Safepoint
。
線程執行到 SafeRegion
時標識自己已經進入到 SafeRegion
,若這段時間JVM發起GC,就不用管這樣狀態的線程了。
在線程離開 SafeRegion
時檢查系統是否已經完成根節點枚舉,如果沒有完成,必須等待收到可以離開 SafeRegion
的信號為止。
垃圾收集器
Serial
串行收集器
原理:
進行垃圾收集工作時,必須暫停其他所有工作線程,即GC線程需要和用戶線程串行。
優點:
簡單高效,Clent
模式下默認新生代收集器。
缺點:
Stop The Word
停頓時間長。
ParNew(頗牛)
多線程串行收集器
原理:
Serial
收集器的多線程版本,GC線程並發執行,暫停所有用戶線程。
特點:
ParNew
作為新生代收集器 可以和 CMS
老年代收集器搭配使用。
是使用 -XX:+UserConcMarkSweepGC
選項後的默認新生代收集器,也可以使用 -XX:+UserParNewGC
選項使用它。
Parallel Scavenge
並行的多線程收集器
Parallel Scavenge
收集器目的是達到一個可控制的吞吐量,吞吐量優先收集器。
吞吐量就是CPU運行用戶代碼時間與CPU總消耗時間的比值。
吞吐量 = 用戶代碼時間 / ( 用戶代碼時間 + 垃圾收集時間 )
CMS
等收集器關註點則在縮短停頓時間。
追求短停頓時間和追求可控吞吐量的區別?
GC停頓時間短是犧牲吞吐量和新生代空間來換取的。
停頓時間短,則可能GC停頓頻繁,整體上用戶代碼時間縮短,吞吐量不高。
停頓時間短適合用戶交互程序;提高吞吐量則高效率利用CPU時間,適合後臺計算任務。
Parallel Scavenge
收集器提供了兩個參數用於精確控制吞吐量。
-XX:MaxGCPauseMillis
最大垃圾收集停頓時間,大於0。-XX:GCTimeRatio
直接設置吞吐量大小,大於0,小於100,默認99,允許最大1%的垃圾收集時間。-XX:+UserAdaptiveSizePoliy
自適應調節策略開關,使用最大停頓時間和吞吐量參數給虛擬機設立一個優化目標。
gc日誌
垃圾收集器參數
jvm筆記-02-垃圾收集器與內存分配策略