垃圾收集器與內存分配策略

垃圾收集器與內存分配策略
  • 垃圾收集器關注的內存是Java堆和方法區
  • 引用計數算法
    • 描述:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的
    • 優勢:實現簡單、判斷效率很高
    • 缺點:很難解決對象之間相互循環引用的問題
  • 可達性分析算法
    • 描述:經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(從GC Roots到這個對象不可達)時,則證實此對象是可回收的
    • 可做爲GC Roots的對象以下
      • 虛擬機棧(棧幀中的本地變量表)中引用的對象
      • 方法區中類靜態屬性引用的對象
      • 方法區中常量引用的對象
      • 本地方法棧中JNI(Native方法)引用的對象
    • Java是經過可達性分析來判斷對象是否存活的
  • 引用
    • 強引用
      相似 Object obj = new Object() 這類的引用,只要強引用還在,垃圾收集器永遠不會回收被引用的對象
    • 軟引用
      • 有用但非必需對象
      • 系統將要發生內存溢出異常以前,會對這些軟引用對象二次回收
      • SoftReference類實現軟引用
    • 弱引用
      • 非必需對象,強度比軟引用更弱
      • 不管當前內存是否足夠,只被弱引用關聯的對象都會被回收
      • WeakReference類實現弱引用
    • 虛引用
      • 幽靈引用或幻影引用
      • 引用關係最弱
      • 不會對對象的生存時間構成影響,沒法經過虛引用取得對象實例
      • 惟一目的:虛引用關聯的對象被收集器回收時收到一個系統通知
      • PhantomReference 類實現虛引用
  • 可達性分析算法之對象死亡
    對象死亡,至少要經歷兩次標記過程
    • 可達性分析後,對象沒有與 GC Roots 相鏈接的引用鏈,第一次標記,並進行一次篩選
      • 篩選的條件:對象是否有必要執行 finalize()方法
        • 對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經被虛擬機調用過,不必執行 finalize() 方法
        • 若斷定爲有必要執行 finalize() 方法,放入 F-Queue 隊列中
          • finalize() 方法是對象逃脫死亡命運的最後一次機會
          • 稍後GC將對 F-Queue 中的對象進行第二次小規模標記
          • 對象在 finalize()方法中拯救本身:從新與引用鏈上的任何一個對象創建關聯便可,在第二次標記時該對象將被移除「即將回收」的集合
          • 第二次標記未逃脫,基本上就真的被回收了
          • 任何一個對象的 finalize() 方法都只會被系統自動調用一次,若是對象面臨下一次回收, finalize() 方法不會被再次執行
  • 回收方法區
    • 方法區垃圾收集主要回收兩部分:廢棄常量和無用類
    • 廢棄常量回收
      以String爲例,若是沒有任何String對象引用常量池中的某一常量,也沒有其餘地方引用這個常量,此時發生內存回收時,有必要的話,該常量就會被系統清理出常量池
    • 無用類回收
      判斷無用類三個條件,都知足時 能夠 被回收:
      • 該類全部的實例都已經被回收,也就是Java堆中不存在該類的任何實例
      • 加載該類的ClassLoader已經被回收
      • 該類對應的java.Lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法
    • 場景
      大量使用反射、動態代理、CGLib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都須要虛擬機具有類卸載的功能
  • 垃圾收集算法
    • 標記-清除算法
      • 最基礎的收集算法
      • 分爲「標記」和」清除」兩個階段
        首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象
      • 缺點
        • 效率問題,標記和清除兩個過程的效率都不高
        • 空間問題,標記清除後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後須要分配較大對象時,沒法找到足夠的連續內存而不得提早觸發另外一次垃圾收集動做
    • 複製算法
      • 描述
        將可用內存按照容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,而後再把已使用過的內存空間一次清洗掉。
      • 優勢
        每次都是對整個半區進行內存回收,內存分配時不用考慮內存碎片等複雜狀況,實現簡單,運行高效
      • 缺點
        代價是將內存縮小爲原來的一半
      • 現代商業虛擬機都採用這種算法回收新生代
        • 將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor
        • 回收時,將Eden和Survivor中還存活的對象一次性複製到另外一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間
        • HotSpot虛擬機默認Eden和Survivor的大小比例是8:1
        • 當Survivor空間不夠用時,須要依賴老年代進行分配擔保
          若是另外一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象將直接經過分配擔保機制進入老年代
    • 標記-整理算法
      • 針對老年代
      • 讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存
    • 分代收集算法
      • 當前商業虛擬機的垃圾收集都採用這種算法
      • 將Java堆分爲新生代和老年代
        • 新生代採用複製算法
        • 老年代採用「標記-清理」或者 「標記-整理」算法
  • 垃圾收集器
    • Serial收集器
      • 新生代收集器
      • 單線程收集器
      • 新生代採用複製算法,老年代採用標記-整理算法
      • 垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束,即「Stop The World」
      • 適用於client模式下
    • ParNew收集器
      • 新生代收集器
      • Serial收集器的多線程版本,多條線程進行垃圾收集
      • 新生代採用複製算法,老年代採用標記-整理算法
      • Stop The World
      • 除Serial收集器以外,只有它能與CMS收集器配合工做
      • 並行:多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態;
      • 併發:用戶線程與垃圾收集線程同時執行
    • Parallel Scavenge收集器
      • 新生代收集器,採用複製算法
      • 並行的多線程收集器
      • 目標是達到一個可控制的吞吐量
        吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
      • 提供兩個參數精確控制吞吐量
        • 控制最大垃圾收集停頓時間 -XX:MaxGCPauseMillis 參數
          • 大於0的毫秒數,保證內存回收花費的時間不超過設定值
          • 值越小,垃圾收集速度越快,但犧牲了吞吐量和新生代空間,新生代空間更小,致使垃圾收集更頻繁,吞吐量下降
        • 垃圾收集時間佔總時間的比例 GCTimeRatio
          大於0且小於100的整數
        • -XX:+UseAdptiveSizePolicy
          • 這個參數打開後,就不須要手工指定新生代的大小、Eden與Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式成爲GC自適應的條件策略
    • Serial Old收集器
      • 老年代收集器
      • 單線程收集器
      • 標記-整理算法
      • 主要意義給Client模式下使用
    • Parallel Old收集器
      • 老年代收集器
      • 多線程收集器
      • 標記-整理算法
      • 吞吐量優先
  • CMS收集器
    • 以獲取最短回收停頓時間爲目標的收集器
    • 標記-清除算法
    • 4個步驟
      • 初始標記
      • 併發標記
      • 從新標記
      • 併發清除
    • 初始標記、從新標記兩個步驟須要Stop The World。併發標記和併發清除過程收集器線程均可以與用戶線程之前工做
    • 優勢:併發收集、低停頓
    • 缺點
      • 對CPU資源很是敏感
        佔用CPU資源,吞吐量下降
      • 沒法處理浮動垃圾,可能出現「Concurrent Mode Failure」 失敗而致使另外一次Full GC產生
        • 浮動垃圾:併發清理階段用戶線程還在運行,還會有新的垃圾不斷產生,沒法在當次收集中處理,只好留待下一次GC時再清理掉
        • 須要預留空間給用戶線程使用,使用參數 -XX:CMSInitiatingOccupancyFraction 能夠調節老年代已使用空間從而觸發收集,但當預留的空間沒法知足用戶線程時,會發生 「Concurrent Mode Failure」 失敗,此時會臨時啓動 Serial Old收集器
      • 因爲基於標記-清除算法,所以會產生大量空間碎片,不利於大對象的分配
  • G1收集器
    • 特色
      • 並行與併發
      • 分代收集
      • 空間整合
      • 可測試的停頓
    • 整個Java堆劃分爲多個大小相等的獨立區域(Region)
      G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region
    • 使用Remembered Set避免全堆掃描
      G1中每一個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region中,若是是,便經過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏
    • 除去Remembered Set操做,G1運做步驟以下:
      • 初始標記
        初始階段僅僅是標記一下GC Roots能直接關聯到的對象,而且修改TAMS的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中建立對象,這階段須要停頓線程,但耗時很短
      • 併發標記
        併發階段是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與用戶程序併發執行
      • 最終標記
        最終標記階段是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set中,這階段須要停頓線程,但可並行執行
      • 篩選回收
        篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃
    • 內存分配與回收策略
      • 對象優先在Eden分配
      • 大對象直接進入老年代
      • 長期存活的對象將進入老年代
        虛擬機給每一個對象定義了一個對象年齡計數器,若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor空間中,而且對象年齡設爲1。對在Survivor區每熬過一次的Minor GC,年齡就增長1歲,當它的年齡增長到必定程度(默認爲15歲),就將會被晉升到老年代
      • 動態對象年齡斷定
        若是在Survivor空間中的相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡
      • 空間分配擔保 在發生Minor GC以前,若是老年代最大可用連續空間小於新生代全部對象的總空間,而且設置了容許擔保失敗,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於,將嘗試一次Minor GC,可是可能會擔保失敗,失敗後也只能從新發起一次Full GC;若是小於,就要進行一次Full GC
相關文章
相關標籤/搜索