JVM垃圾收集器與內存分配策略
文章目錄
1)JVM垃圾收集器
2)HotSpot的算法實現
3)內存分配與回收策略
JVM垃圾收集器
- 對於Java堆和方法區,我們只有在程序運行期間才能知道會創建那些對象,這部分內存的分配和回收都是動態的,垃圾收集器所關注的正是這部分內存。
- 判定對象是否存活(會回收哪些對象)
- 若一個對象不被任何對象或變量引用,那麼他就是無效對象,需要被回收
- 引用計數法
- 爲變量設置一個計數器,有一個變量指向它,則計數器加1,不再指向它,則計數器減1
- 實現簡單效率高,但無法解決遇到對象互相引用的場景
- 可達性分析法
- 和 GC Roots 直接或間接關聯的對象都是有效對象,和 GC Roots 沒有關聯的對象就是無效對象。
- 建立一個根節點,並構建一個引用圖,當需要判斷誰是垃圾時,從根節點進行遍歷,沒有遍歷到的則是垃圾對象,否則就是有用對象
- 引用的種類
- 1.強引用
- 類似"Object obj = new Object()"
- 垃圾收集器永遠不會回收被引用的對象,寧願拋出OutOfMemoryError
- 2.軟引用
- 內存不夠會被回收
- 有用但非必需
- 3.弱引用
- 無論內存是否足夠都會被回收
- 非必需
- 4.虛引用
- 在任何時候都可能被回收
- 1.強引用
- 垃圾收集算法 (如何回收對象)
- 1.標記-清除算法
- 先標記出所有需要回收的對象,然後統一回收所有被標記過的對象
- 容易產生大量不連續內存碎片,如果不連續的碎片過多的話,會導致一些大的對象存不進去
- 2.複製算法
- 將可用內存劃分爲兩塊,每次只使用其中的一塊,當着一塊快用完的時候,就會觸發垃圾回收,把存活的對象全部複製到另一塊內存中去,然後清理這塊內存。
- 優化:HotShot虛擬機是默認按8:1的比例來分配的
- 3.標記-整理算法
- 把垃圾清除之後,會讓存活的對象往一個方向靠攏,以此來整理碎片
- 4.分代收集算法
- 根據對象存活週期不同,將Java堆劃分成新生代和老年代,並根據各個年代的特點採用最適當的收集算法
- 新生代:大批對象死去,只有少量存活。使用"複製算法",只需複製少量存活對象即可。
- 老年代:對象存活率高。使用"標記—清理算法"或者"標記—整理算法",只需標記較少的回收對象即可。
- 1.標記-清除算法
HotSpot的算法實現
- HotSpot如何發起回收
- 枚舉根節點
- 主流Java虛擬機使用的都是準確式GC,在執行系統停頓之後無需檢查所有執行上下文和全局的引用位置,而是通過一些辦法直接獲取到存放對象引用的地方,在HotSpot中是通過一組稱爲OopMap的數據結構來實現的,完成類加載後會計算出對象某偏移量上某類型數據、JIT編譯時會在特定的位置記錄棧和寄存器中是引用的位置。這樣GC在掃描時就可直接得知這些信息,並快速準確地完成GC Roots的枚舉。
- 安全點
- 安全點時可以讓程序長時間執行的地方,如方法調用、循環跳轉、異常跳轉等具有指令序列複用的特徵。
- 程序執行時並非在所有地方都停頓執行GC,只在到達安全點時才暫停
- 在安全點上停頓的方案
- 搶佔式中斷
- 主動式中斷
- 安全區域
- 引用關係不會發生變化的一段代碼片段,在安全區域中的任意地方開始GC都是安全的,可看做是擴展的安全點。
- 安全點機制只能保證程序執行時,在不太長的時間內遇到可進入GC的安全點,但在程序不執行時(如線程處於Sleep或Blocked狀態)線程無法響應JVM的中斷請求
- GC收集器 (具體的回收動作)
-
- Serial收集器
- ParNew
- Parallel Scavenge
- 吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
- CMS收集器
- 標記-清除算法
- G1收集器
- 複製算法+標記-整合算法
- 枚舉根節點
內存分配與回收策略
- 對象的內存分配,往大的方向說,就是在堆上分配,對象主要分配在新生代的Eden區上,如果啓動了本地線程分配緩衝,則線程優先在TLAB(Thread Local Allocation Buffer,即線程本地分配緩存區)上分配。少數情況下會直接分配在老年代中,分配的規則不是百分百固定的。
- 內存分配規則
- 對象優先在Eden分配
- 大多數情況下對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時虛擬機將發起一次Minor GC
- Minor GC:回收新生代(包括 Eden 和 Survivor 區域),因爲 Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快。
- Major GC / Full GC: 回收老年代,出現了 Major GC,經常會伴隨至少一次的 Minor GC,但這並非絕對。Major GC 的速度一般會比 Minor GC 慢 10 倍 以上。
- 大對象直接進入老年代
- 對於需要大量連續內存空間的Java對象(如很長的字符串以及數組),如果大於虛擬機設定的-XX:PretenureSizeThreshold參數值將直接在老年代分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存複製。
- 長期存活的對象將進入老年代
- 虛擬機會給每個對象定義一個年齡計數器,當對象在Eden出生並經過第一次Minor GC後仍存活且能被Survivor容納的話,將被移動到Survivor空間中並將對象年齡設爲1;當對象在Survivor區中每“熬過”一次Minor GC年齡就+1,直至增加到一定程度(默認爲15歲,可通過-XX: MaxTenuringThreshold設置)就會被晉升到老年代中。
- 動態對象年齡判定
- 爲了能更好地適應不同程序的內存狀況,虛擬機並不要求一定要達到-XX: MaxTenuringThreshold設置值才能晉升到老年代,當Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,那麼年齡大於或等於該年齡的對象可以直接進入老年代。
- 空間分配擔保
- 在發生Minor GC之前虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,若是,說明可確保Minor GC是安全的,反之虛擬機會查看-XX:HandlePromotionFailure設置值是否允許擔保失敗;若允許,會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小;若大於,將嘗試進行一次Minor GC,若小於或者不允許擔保失敗,將改爲進行一次Full GC。
- 對象優先在Eden分配
- 內存分配規則
參考
《深入理解Java虛擬機》
內存分配與回收策略
要點提煉| 理解JVM之GC&內存分配