JVM 垃圾回收

引用類型

  1. 強引用:發生 gc 的時候不會被回收java

  2. 軟引用:有用但不是必須的對象,在發生內存溢出以前會被回收算法

  3. 弱引用:有用但不是必須的對象,在下一次 GC 時會被回收服務器

  4. 虛引用(幽靈引用/幻影引用):沒法經過虛引用得到對象markdown

    用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知多線程

垃圾辨別方法

  1. 引用計數器
    • 爲每一個對象建立一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1
    • 當計數器爲 0 時就能夠被回收。缺點是不能解決循環引用的問題
  2. 可達性分析
    • 從 GC Roots 開始向下搜索,搜索所走過的路徑稱爲引用鏈
    • 當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是能夠被回收的

GC Roots,GC 的根集合, 是一組必須活躍的引用併發

可做爲 GC Roots 的對象有:性能

  1. 虛擬機棧(棧幀中的本地變量表)中引用的對象
  2. 方法區中類靜態屬性引用的對象
  3. 方法區中常量引用的對象
  4. 本地方法棧中 JNI(即通常說的 native 方法)中引用的對象

垃圾收集算法

  1. 引用計數(Reference Counting)spa

    • 原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數
    • 垃圾回收時,只用收集計數爲 0 的對象
    • 缺點:沒法處理循環引用問題
  2. 標記-清除(Mark-Sweep)線程

    • 第一階段從引用根節點開始標記全部被引用的對象
    • 第二階段遍歷整個堆,把未標記的對象清除
    • 缺點:此算法須要暫停整個應用,同時,會產生內存碎片
  3. 複製(Copying)指針

    • 把內存空間劃爲兩個相等的區域,每次只使用其中一個區域
    • 垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。每次只處理正在使用中的對象
    • 由於複製成本比較小,同時複製過去之後還能進行相應的內存整理,不會出現「碎片」問題
    • 缺點:須要兩倍內存空間
  4. 標記-整理(Mark-Compact)

    • 第一階段從引用根節點開始標記全部被引用對象
    • 第二階段遍歷整個堆,將全部存活的對象都向一端移動,而後直接清除掉端邊界之外的內存
    • 此算法避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題
  5. 分代(Generational Collecting)

    • 基於對對象生命週期分析後得出的垃圾回收算法
    • 把堆中對象分爲年青代、年老代、持久代(JDK8 不存在持久代)
    • 按照對象經歷的 GC 次數來肯定是哪一代
    • 對不一樣代使用不一樣的算法進行回收
    • 如今的垃圾回收器通常使用此算法,是多種基本回收算法的組合使用

分代回收算法

起源:研究發現,大部分 java 對象只存活一小段時間,而存活下來的小部分 java 對象則會存活很長一段時間

簡單來講,將堆分紅兩部分,年輕代用來存放新對象,當對象存活時間夠長時,移動到年老代

堆的分代

  1. 年輕代 Young Generation

    • 默認佔總空間的 1/3(經過 -XX:NewRatio 指定年輕代和老年代比例)
    • 分爲 Eden、To Survivor、From Survivor 三個區,默認佔比 8:1:1(經過 -XX:SurvivorRatio 指定)
  2. 年老代 Tenured Generation

    • 默認佔總空間的 2/3
  3. 持久代 Perm Generation(JDK8後不存在)

    • 即方法區,用於存放靜態文件,現在Java類、方法等
    • 持久代對垃圾回收沒有顯著影響
    • 在 JDK8 中,廢棄了持久代,改用元空間(metaspace)實現方法區,屬於本地內存

分代收集

  1. 年輕代回收器

    • 假設大部分對象都存活很短期,須要頻繁採用耗時較短的垃圾回收算法

    • 新生代垃圾收集器通常採用複製算法,優勢是效率高,缺點是內存利用率低

    • 垃圾收集器有:Serial、ParNew、Parallel Scavenge

  2. 年老代回收器

    • 假設老年代中的對象大機率繼續存活,真正觸發老年代 gc 時,表明假設出錯或堆空間已耗盡,通常須要全堆掃描,全局垃圾回收
    • 老年代收集器通常採用的是標記-整理的算法進行垃圾回收
    • 垃圾收集器有:Serial Old、Parallel Old、CMS
  3. 整堆回收器

    G1:兼顧吞吐量和停頓時間的 GC 實現,JDK 9 之後的默認 GC 選項

回收過程

新對象存放在年輕代的 Eden 分區,Eden 空間耗盡時,觸發 gc,通常使用複製算法

年老代空間佔用到達某個值以後就會觸發全局垃圾收回,通常使用標記整理算法

  1. 把 Eden 和 From Survivor 存活的對象放入 To Survivor 區
  2. 清空 Eden 和 From Survivor 分區
  3. From 和 To 交換指針,保證下次 gc 前To Survivor 爲空
  4. Survivor 分區的對象,通過一次複製年齡就 +1,年齡到達 15時(默認 15),Survivor 分區升級爲老生代。對象也會直接進入年老代

gc 類型

  1. Minor GC

    • 通常狀況下,當新對象生成,而且在 Eden 申請空間失敗時,就會觸發Minor GC
    • 在年輕代 Eden 區域進行GC,清除不存活對象,而且把尚且存活的對象移動到 Survivor 區。而後整理 Survivor 的兩個區
    • 很頻繁的 gc,不影響老年代
  2. Full GC

    對整個堆進行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,所以應該儘量減小Full GC。有以下緣由可能致使Full GC:

    • Tenured 被寫滿
    • Perm 域被寫滿(JDK8 以前)
    • System.gc( ) 被顯示調用
    • 上一次 GC 以後堆的各域分配策略動態變化

垃圾收集器

收集器分類

  1. 串行收集器

    • 使用單線程處理全部垃圾回收工做,由於無需多線程交互,因此效率比較高

    • 沒法使用多處理器的優點,因此適合單處理器機器,也能夠用在小數據量狀況下的多處理器機器

    • 可使用 -XX:+UseSerialGC 打開

  2. 並行收集器

    • 對年輕代進行並行垃圾回收,能夠減小垃圾回收時間。通常在多線程多處理器機器上使用

      使用 -XX:+UseParallelGC 打開

    • 並行收集器 jdk5 引入,在 jdk6 中進行了加強,可對堆年老代進行並行收集

      使用 -XX:+UseParallelOldGC 打開

    • 若是年老代不使用併發收集,而使用單線程進行垃圾回收,會制約擴展能力

  3. 併發收集器

    • 能夠保證大部分工做都併發進行(應用不中止),垃圾回收只暫停不多的時間
    • 此收集器適合對響應時間要求比較高的中、大規模應用
    • 使用 -XX:+UseConcMarkSweepGC 打開

常見收集器

  1. Serial:最先的單線程串行垃圾回收器
  2. Serial Old:Serial 垃圾回收器的老年版本,一樣也是單線程的,能夠做爲 CMS 垃圾回收器的備選預案
  3. ParNew:是 Serial 的多線程版本
  4. Parallel :
    • 和 ParNew 收集器相似,是多線程的收集器
    • Parallel 是吞吐量優先的收集器,能夠犧牲等待時間換取系統的吞吐量
  5. Parallel Old:
    • 是 Parallel 老生代版本
    • Parallel 使用複製算法,Parallel Old 使用標記-整理算法
  6. CMS:一種以得到最短停頓時間爲目標的收集器,很是適用 B/S 系統
  7. G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 之後的默認 GC 選項

CMS 收集器

  1. CMS:Concurrent Mark-Sweep

    • 犧牲吞吐量來得到最短回收停頓時間

    • 很是適合用在要求服務器響應速度的應用上

    • 使用 -XX:+UseConcMarkSweepGC 來指定使用 CMS 垃圾回收器

  2. CMS 使用標記-清除的算法

    • 在 gc 時候會產生大量的內存碎片
    • 當剩餘內存不能知足程序運行要求時,系統將會出現 Concurrent Mode Failure
    • 臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的性能將會被下降
相關文章
相關標籤/搜索