JVM基礎之垃圾回收和內存分配(二)

“合抱之木,生於毫末;千里之行,始於足下;九層之臺,起於壘土。”

一、堆內對象引用分析

  1. 引用計數法
  • 引用計數法(Reference Counting)的實現簡單,判定效率高,目前是python使用的GC算法。
  • 其爲對象添加一個引用計數器,每當有一次引用,計數器就加1;引用失效時,計數器就減1;任何時刻計數器爲0的對象就是不可能再被使用的。
  • 由於它很難解決對象之間互相循環引用的問題,因此沒有被主流的Java虛擬機選用。
  1. 可達性分析算法
  • 以“GC Roots”對象作爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈。
  • 當一個對象到GC Roots沒有任何引用鏈時,則證明該對象是不可用的。
  1. 引用的類型
  • 強引用(Strong Reference)只要還存活,垃圾收集器就永遠不會回收。
  • 軟引用(Soft Reference)關聯的對象,在系統將要發生內存溢出之前,將會把這些對象列進回收範圍進行第二次回收,如果第二次回收後還是沒有足夠的內存,纔會拋出OOM異常。
  • 弱引用(Weak Reference)關聯的對象,在垃圾收集器工作時,無論當前內存是否足夠,都一定會被回收。
  • 虛引用(Phantom Reference)只是了爲了關聯的對象在被垃圾收集器回收時收到系統通知。
  1. finalize()方法
  • 當對象被垃圾收集器第一次標記時執行該方法,將對象放入F-Queue隊列,並有虛擬機建立的低優先級的Finalizer線程執行,對象進行“自救”。稍後GC將對F-Queue中的對象進行第二次小規模的標記,如果對象已經被標記,則進行回收。
  • finalize()方法在對象的生命週期內只會執行一次。
  • 不推薦使用該方法。

二、對象回收機制

  1. 標記-清除
  • 分爲“標記”和“清除”兩個階段,是最基礎的收集算法。
  • 不足之處有兩個,一是效率問題,二是會造成內存空間產生大量不連續的內存碎片。
  1. 標記-整理
  • 分爲“標記”和“整理”兩個階段,主要適用於老年代中進行內存回收。
  • 首先標記對象,然後讓所有存活對象向一端移動,然後直接清理邊界以外的內存。
  1. 複製
  • 爲了解決效率問題,提出了“複製”收集算法,它將可用內存劃分爲大小相等的兩塊,每次使用其中一塊。
  • 兩塊內存中一塊用完時,進行標記,將存活的對象複製到另一塊中,並將已使用過得內存空間清理。
  • 缺點是會浪費一半的內存。根據研究發現,98%的對象都是朝生夕死,所以又進一步劃分爲新生代Eden和Survivor空間,比例爲8:1,將新生代中存活的對象複製到Survivor空間中,當Survivor空間不足時,依賴老年代進行分配擔保。
  1. 分代收集
  • 當今商業虛擬機的垃圾回收機制,根據對象存活週期的不同將內存劃分爲幾塊,再分別選擇適合的收集算法對各個分塊進行回收。
  • Java中新生代使用“複製算法”,老年代使用“標記-清楚”或“標記-整理”。

三、垃圾收集器

  1. Serial
  • 最基本、發展歷史最悠久的收集器,在新生代工作,是一個單線程的收集器,在進行垃圾收集時,必須暫停其他所有的工作現場,直到收集結束。
  1. ParNew
  • 是Serial收集器的多線程版本,在新生代工作,收集算法、Stop The World、對象分配規則、回收策略等都與Serial完全一樣。
  • 它是Server模式下的虛擬機中首選的收集器,目前能與CMS配合的新生代收集器只有它。
  1. Parallel Scavenge
  • Parallel Scavenge收集器是新生代收集器,使用複製算法,是並行的多線程收集器,又稱“吞吐量優先”收集器。
  • 它關注的目標是達到一個可控制的吞吐量(Throughput,吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),如虛擬機總運行時間100分鐘,其中垃圾收集時間1分鐘,那麼吞吐量就是99%)。
  • -XX:MaxGCPauseMillis控制最大停頓時間,-XX:GCTimeRatio設置吞吐量的大小。不要認爲把停頓時間設置的很小會使垃圾收集速度變快,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的。
  1. Serial Old
  • 它是Serial收集器的老年代版本,同樣是一個單線程收集器,使用標記-整理算法,主要是作爲Client模式下的虛擬機使用。
  • 當CMS發生Concurrent Mode Failure時觸發它來進行FullGC。
  1. Parallel Old
  • 它是Paralled Scavenge收集器的老年代版本,使用多線程和標記-整理算法。
  • 與Paralled Scavenge配合,實現真正的“吞吐量優先”收集器。
  1. CMS收集器
  • CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,主要應用在互聯網站的服務端上,尤其重視服務的響應速度和系統停頓時間最短,基於“標記-清除”算法。
  • 由四個步驟完成回收:初試標記、併發標記、重新標記、併發標記。
  • 有三個明顯的缺點,一是對CPU資源敏感,會因爲分配運算能力去執行收集器線程而導致用戶程序執行速度降低;二是無法處理“浮動垃圾(Floating Garbage)”,可能會出現“Concurrent Mode Failure”失敗導致Full GC發生;三是因爲基於“標記-清除”算法,所以會導致大量的空間碎片,爲解決這個問題,CMS提供了-XX:+UseCMSCompactAtFullCollection,用於在CMS收集器頂不住要進行FullGC時開啓內存碎片合併整理過程,還提供了-XX:CMSFullGCsBeforeComaction設置執行多少次不壓縮FullGC後進行一次帶壓縮的FullGC。
  1. G1收集器
  • G1(Garbage First)收集器是當今收集器技術發展的最前沿成果之一,由2004年Sun實驗室發表的論文提出,是一款面向服務端應用的垃圾收集器,HotSpot開發團隊賦予它的使命是替換掉CMS收集器。
  • 並行與併發:充分利用多個CPU和多核來縮短Stop-The-world停頓時間,並且可以在GC動作時保證Java程序繼續執行。
  • 分代收集:分代概念在G1中依舊存在,G1不需要其他收集器配合就能夠獨立管理整個GC堆。
  • 空間整合:G1從整體看是基於“標記-整理”算法實現收集器,從局部看是基於“複製”算法實現。它將堆空間劃分爲多個大小相同的獨立區域(Region),依舊保留新生代和老年代的概念,但兩者不再是物理隔離的,它們都是一部分Region的集合。
  • 可預測的停頓:G1除了追求低停頓,還建立可預測的停頓時間模型。

四、內存分配與回收策略

  1. 新生代策略
  • 大多數情況下,對象在新生代Eden區分配(大對象會直接分配到老年代),當Eden區沒有足夠內存時,發起一次MinorGC。
  1. 老年代策略
  • 較大的對象分配到老年代,我們應當儘量避免短生命的大對象產生,通過-XX:PretenureSizeThreshold參數可以設置直接進入老年代大對象的閾值。
  • 長期存活的對象將進入老年代,虛擬機爲每個對象定義一個對象年齡計數器。對象在Eden出生並經歷第一次MinorGC後仍然存活,並且能被Survivor容納,就將對象移動到Survivor中,並且設對象年齡爲1,對象每次在Survivor區存活過一次MinorGC,年齡就加1,當年齡增長到一定程度(默認爲15)就會晉升到老年代中,晉升老年代的閾值可以通過-XX:MaxTenuringThreshold設置。
  • 動態對象年齡判定,當Survivor空間中相同年齡的對象超過空間的一半,則大於等於該年齡的對象都直接進入老年代,無需等待到達MaxTenuringThreshold閾值。
  • 空間分配擔保,在MinorGC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果條件成立則MinorGC是可以確保安全的。如果不成立,查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,檢查老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小。如果大於就嘗試一次MinorGC,如果小於或者不允許擔保失敗,則要進行一次FullGC。如果嘗試MinorGC失敗,則進行一次FullGC,通常HandlePromotionFailure被打開,爲了避免頻繁進行FullGC。JDK1.6之後HandlePromotionFailure被廢棄,只要老年代的連續最大空間大於新生代對象總大小或者歷次晉升老年代對象的平均大小就進行MinorGC,否則進行FullGC。

說明

  • 文中內容主要來自於《深入理解Java虛擬機》,更多內容請關注原著。
  • 此文是讀書時對重點知識的記錄,如有錯誤請一定指出。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章