【JVM學習筆記】垃圾收集器與記憶體分配策略

成為靠譜的程式設計師發表於2020-11-28

垃圾收集器與記憶體分配策略

物件已死?(判斷物件是否可以回收)

引用

  • reference型別的資料中儲存的數值代表的是另一塊記憶體的起始地址

    • 強引用:程式碼中的引用賦值 例如 new物件;意義:物件不會被回收
    • 軟引用:還有用非必須的物件;意義:在系統將要發生記憶體溢位前回收
    • 弱引用:非必須物件;意義:每次垃圾回收時都會被清理
    • 虛引用:最弱的一種引用;意義:可以讓一個物件被回收時收到一個系統通知

引用計數演算法

  • 在物件中新增一個引用計數器,被引用加一,引用失效減一,為零就不會再使用可以回收
  • 優點:原理簡單,效率高
  • 缺點:實用性不強,例如 很難解決物件間的互相迴圈引用的問題

可達性分析演算法

  • 判斷某個物件到GC Roots間有沒有引用鏈相連,如果沒有就說明物件不可達、不再使用可以回收

  • GC Roots

    • 虛擬機器棧的本地變數表中引用的物件
    • 方法區中類靜態屬性引用的物件
    • 方法區中常量引用的物件
    • 本地方法棧中JNI(Native方法)引用的物件
    • java虛擬機器內部的引用,如基本資料型別對應的Class物件,常駐的異常物件,類載入器
    • 同步鎖持有的物件
    • 反應虛擬機器內部情況的JMXBean、JVMTI中註冊的回撥、原生程式碼快取等

生存還是死亡(判斷物件是否真的能被回收)

  • 至少要經歷兩次標記(判斷),如果第一次被標記成不可達,但是在第二次標記前又與GC Roots建立了連結就不會被清理

回收方法區

  • 《java虛擬機器規範》不強制方法區實現垃圾收集
  • 方法區垃圾收集價效比比較低
  • 主要回收廢棄的常量和不再使用的型別(型別指載入到記憶體中的Class)

垃圾收集演算法

引用計數式垃圾收集(直接垃圾收集)

追蹤式垃圾收集(間接垃圾收集)(主流)

  • 分代收集理論

    設計原則:收集器應該將java堆劃分出不同區域,然後將回收物件依據其年齡分配到不同的區域之中儲存

    • 弱分代假說:絕大多數物件都是朝生夕死的

    • 強分代假說:熬過越多次垃圾收集過程的物件越你難以死亡

    • 分代

      • 新生代
      • 老年代
    • 跨分代引用假說:存在相互引用關係的物件應該傾向於同生同死,跨代引用佔比較少。

      • 存在問題:當掃描新生代物件時如果有跨代引用還需要掃描整個老年代,成本較高

        • 解決辦法:新生代建立一個全域性資料結構(記憶集),這個結構將老年代劃分成若干小塊,標識出老年代的哪一塊記憶體存在跨代引用。Minor GC時只有包含了跨代引用的小塊記憶體裡的物件才會被加入到GC Roots進行掃描,而不用掃描整個老年代
  • 標記-清除演算法:標記後清除被標記的物件

    • 缺點:1、執行效率不穩定(受要標記的目標物件數量的限制);2、記憶體空間碎片化(產生不連續的記憶體碎片)
  • 標記-複製演算法:將記憶體劃分為大小相等的兩塊,每次只用其中一塊,當記憶體用完了就將存活物件複製到另一塊,然後將已使用的記憶體一次清理掉

    • 缺點:空間浪費太嚴重
    • 新生代優先採用此演算法,而老年代不會使用這種方法,因為新生代98%物件都是朝生夕死的,這種情況實現簡單、效率高
  • 標記-整理演算法:先標記,讓存活物件都移動到記憶體一端,然後清理掉邊界以外的記憶體

經典垃圾收集器

Serial收集器

  • 單執行緒收集器
  • 優點:簡單高效、記憶體消耗最小
  • 缺點:工作時必須“Stop The World”,使用者體驗差

ParNew收集器

  • Serial收集器多執行緒並行版本
  • HotSpot虛擬機器中第一款退出歷史舞臺的垃圾收集器,只能和CMS配合使用而CMS將會替代CMS

Parallel Scavenge收集器

  • 多執行緒,基於標記-複製演算法
  • 設計目標:達到一個可控的吞吐量(也就是控制垃圾回收所佔用的時間)

Serial Old收集器

  • Serial的老年代版本
  • 單執行緒,基於標記整理演算法

Parallel Old收集器

  • Parallel Scavenge收集器老年代版本
  • 多執行緒,基於標記-整理演算法

CMS收集器

  • 目標:為了獲取最短回收停頓時間

  • 多執行緒,基於標記-清除演算法

  • 缺點:1、處理器資源敏感,會佔用一部分資源導致程式變慢,總吞吐量降低。2、無法處理浮動垃圾,可能導致full gc發生。3、無法處理空間碎片,只能依靠full gc

    浮動垃圾:CMS標記和清理階段使用者執行緒會同時執行併產生新的垃圾,因為有些垃圾在標記後產生所以本次無法清理,只能等到下次。

Garbage First收集器(G1)

  • 將堆記憶體劃分為多個大小相等的獨立區域(Region),每個區域都可以根據需要扮演新生代的Eden空間、Survivor空間或老年代空間,每次回收時G1會判斷那塊回收價效比最高,收益最大;Region中設有Humongous區域,用來儲存大物件(超過Region一半的物件),而超級大物件會被存放在多個連續的Humongous區域

  • 與CMS比較

    • 優勢:1、不會產生垃圾碎片;2、創新性設計帶來較多紅利:可以指定最大停頓時間,分割槽域的記憶體佈局,按收益動態確定回收集
    • 弱勢:1、佔用記憶體多、負載率高

基礎故障處理工具

jps:虛擬機器程式狀況工具,主要用來檢視進行的唯一ID

jstat: 虛擬機器統計資訊監視工具,主要用於檢視虛擬機器各種執行狀態資訊

jinfo:java配置資訊工具,主要使用-sysprops 列印System.getProperties()的內容

jmap:java記憶體映像工具,主要用於生成堆轉儲快照

jhat:虛擬機器堆轉儲快照分析工具,圖形化工具更適合這個工作 所以這個命令用處不大

jstack:java堆疊跟蹤工具,主要用於生成虛擬機器當前時刻執行緒快照,例如可以檢視沒有相應的執行緒在後臺幹什麼

記憶體回收策略

在經典分代設計下,新生物件通常會分配在新生代中,少數情況下也可能直接分配在老年代

物件優先在新生代Eden區域分配

大物件直接進入老年代

長期存活物件進入老年代

  • Eden中的物件經過一次GC仍然存活並且能被Survivor容納就會被轉移到Survivor中,並將物件年齡設定為1,物件在Survivor中每熬過一次GC就增加一歲,年齡增加到15(預設 可以設定)時進入老年代

動態物件年齡判定

  • HotSpot虛擬機器並不是永遠等到物件年齡達到標準才晉升到老年代,如果Survivor空間中的相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就直接進入老年代

選擇合適的垃圾收集器

垃圾收集器工作內容

  • 垃圾收集、堆記憶體的管理與佈局、物件的分配、與直譯器的協作、與編譯器的協作,與監控子系統的協作等等

應用只要執行數分鐘甚至數秒,只要虛擬機器能正確分配記憶體,在堆耗盡之前就會退出

  • Epslion收集器,不進行垃圾回收,但負載極小

收集器的權衡

  • 應用程式的關注點

    • 吞吐量
    • 延遲
    • 記憶體佔用

虛擬機器及垃圾收集器日誌

  • 1、檢視GC基本資訊:jdk9以前 -XX:+PrintGC,9以後 -Xlog:gc
  • 2、檢視GC詳細資訊:9以前 -XX:+PrintGCDetails,9以後 -X-log:gc*
  • 3、檢視GC前後堆、方法區可用容量變化:9以前-XX:+PrintHeapAtGC,9以後 -Xlog:gc+heap=debug
  • 4、檢視GC過程中使用者執行緒併發時間以及停頓的時間:9以前-XX:Print-GCApplicationConcurrentTime 及 -XX:+PrintGCApplicatiionStoppedTime,9以後 -Xlog:safepoint
  • 只列出一些常用的命令

相關文章