JVM垃圾回收

来源:https://www.cnblogs.com/CodeMLB/archive/2019/12/28/12113279.html
-Advertisement-
Play Games

垃圾回收與記憶體分配策略 "垃圾回收與記憶體分配策略" "“垃圾”的定義" "對象是否為“垃圾”" "何為“引用” 四種引用類型" "最後的掙扎 finalize()方法" "回收方法區" "垃圾回收演算法" "回收的前置 分代理論" "標記 清除演算法(Mark Sweep)" "標記 複製演算法" "標記 ...


垃圾回收與記憶體分配策略

“垃圾”的定義

對象是否為“垃圾”

判斷對象是否已成為“垃圾”的兩種方法:引用計數法可達性分析演算法

  • 引用計數法

如果一個對象被引用一次,則加1,如果沒人引用則被回收;存在問題:如果兩個對象迴圈引用,但是沒有任何外部對象引用他們倆,則那兩個對象無法被回收。

  • 可達性分析演算法(主流JVM採用)

沒有被根對象(GC ROOT)直接或簡介引用的對象則會被回收
根對象--肯定不能對回收的對象
GC ROOT對象:system class、同步鎖、線程類、本地方法類

何為“引用”--四種引用類型

JDK1.2以後將引用分為:強引用、軟引用、弱引用和虛引用4種,強度依次減弱。

  • 強引用
    被GC ROOT直接引用(等號賦值
  • 軟引用
    被GC ROOT間接引用;當記憶體不足時被回收,記憶體充足時不會被回收
  • 弱引用
    沒有GC ROOT直接引用,當發生垃圾回收時,不管記憶體是否充足都會被回收
  • 虛引用
    沒有GC ROOT直接引用,虛引用使用時必須配合引用隊列進行管理。

比如創建一個ByteBuffer實現類對象時,會創建個一個Cleaner對象,當ByteBuffer實現類對象沒有再被引用時,ByteBuffer實現類對象會被回收,Cleaner對象則會進入引用隊列,這時候一個referencehandles線程會查找引用隊列中是否存在cleaner對象,如果有則調用Cleaner.clean方法,clean方法則根據記錄的直接記憶體的地址,調用unsafe.freememory方法釋放直接記憶體

  • 補充:引用隊列

軟引用、弱引用本身也要占用一定記憶體,當軟引用、弱引用的引用對象都被回收時,則進入引用隊列,會對引用隊列進行後續管理;虛引用引用的對象被釋放後,虛引用會進入引用隊列

最後的掙扎--finalize()方法

即使可達性分析後,對象被判定為“垃圾”,也並非非死不可。一個對象的死亡至少需要兩次標記:

沒有與GC Root的引用鏈,標記一次
對象沒有重寫finalize()方法,或finalize()重寫但已被調用過一次,標記第二次

如果重寫了finalize()方法,且還沒有被調用,那麼對象會被放置在F-Queue的隊列中,會有一條虛擬機自建的、優先度較低的線程Finalizer線程去執行對象的finalize()方法,但為了防止finalize()方法出現死迴圈等異常,並不會保證等待finalize()方法執行結束。在此期間,若對象建立了引用鏈,則對象可以存活一次,否則就“死定了”。

不建議使用該finalize()方法

回收方法區

方法區的垃圾回收主要包含兩部分:廢棄的常量、不再使用的類型

常量的回收類似與Java堆中的對象,當沒有引用時,則允許回收
類型的回收相對比較苛刻,需要同時滿足以下條件,才允許被回收

  • 該類所有實例都已被回收
  • 該類的類載入器已被回收
  • 該類對應的java.lang.Class對象沒有被引用,且在任何地方都不可以通過反射訪問該類方法

垃圾回收演算法

從判定垃圾消亡的角度出發,垃圾回收演算法可以劃分為“引用計數式垃圾收集”、“追蹤式垃圾收集”兩類。在Java虛擬機中的討論都在追蹤式垃圾收集的範疇中。

回收的前置--分代理論

分代設計的理論建立在兩個分代假說之上:

  1. 弱分代假說:新生對象都是朝生夕死
  2. 強分代假說:熬過越多次垃圾回收的對象,就越難以消亡

    設計原則:

    垃圾收集器應該依據對象的年齡,把Java堆劃分為不同的區域。

    • 新生代
      朝生夕滅的對象集中在一個區域,每次回收只需關註少量需要存活的對象即可
    • 老年代
      難以消亡的對象集中在一個區域,可以使用較低的頻率去觸發回收機制

    但是,在對新生代進行垃圾收集的時候,不免會出現新生代的中的對象被老年代引用的情況。所以,為了確定新生代區域的存活對象,除了GC Root之外還需要遍歷整個老年代中所有對象來獲得準確的可達性分析。基於此,引入第三條經驗法則:

  3. 跨代引用假說:跨代引用相對於同代引用來說只占少數

    跨代引用一般傾向於兩個對象同時生存或同時消亡的

    設計原則:

    在新生代建立全局數據結構(記憶集),把老年代分為若幹小塊,記錄老年代中哪一塊記憶體存在跨代引用
    此後,發生minor gc時只有包含了跨代引用的小塊記憶體中的對象才會被加入到GC Root進行掃描

標記-清除演算法(Mark Sweep)

先標記需要回收的對象,再統一清除

效率不穩定,隨著對象數量增多,標記、清除兩個過程的執行效率降低
記憶體碎片化,導致存入大對象時無法獲得足夠的連續記憶體空間,觸發另一次垃圾收集動作

標記-複製演算法

將可用記憶體劃分為兩個完全相等空間,每次只使用其中的一塊。如果其中的一塊記憶體用完,則將存活的對象完全複製到另一塊,再對原來的空間進行統一清除回收。

  • 缺點
    記憶體空間的浪費
    若空間內大量對象都是存活的,複製的開銷增大
  • 優點
    簡單高效
    不用考慮記憶體空間碎片化
    PS.
    現商用Java虛擬機多在新生代中採用該方法
  • Appel式回收
    HotSpot虛擬機中的Serial、ParNew等新生代收集器均採取該策略。具體如下:
    把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配只使用Eden和一塊Survivor,發生垃圾回收時,將存活的對象一次性複製給另一塊Survivor空間內,然後清理已用的空間。

    HotSpot虛擬機給Eden和Survivor預設大小比例為8:1,也就是說會有10%的空間會被浪費。當預留的10%的記憶體空間存不下存活的對象時,,就需要依賴其它記憶體空間(大多為老年代)進行記憶體分配。

標記-整理演算法(Mark Compact)

區別與標記--清除演算法,標記--整理演算法,在標記後將存活的對象移向一端,然後將另一端的空間整體回收,是一種移動式的演算法。

  • 優點
    不存在碎片化記憶體,則無需依賴複雜的記憶體分配器
  • 缺點
    對象的移動操作需要觸發“Stop The World”耗時較久

標記?清除:整理

標記-清除是一種非移動式演算法、標記-整理是一種移動式演算法,兩者比較說明:

  • 吞吐量比較
    吞吐量定義:賦值器和收集器效率之和
    不移動會使得收集器效率增大,但是記憶體分配和訪問會比垃圾回收頻率高得多,所以整體吞吐量還是降低的。

  • 舉例說明
    HotSpot虛擬機中關註吞吐量的Parallel Scavenger收集器基於標記-整理演算法;關註低延遲的CMS收集器基於標記-清除演算法

  • 混合方案
    使虛擬機多數時間採用標記-清除演算法,暫時容忍碎片的存在,等到碎片化程度開始影響對象的記憶體分配時,在採用標記-整理演算法收集一次(CMS就採取該方式)

經典垃圾回收器

所謂“經典”垃圾回收器是指區別於實驗室階段的、已通過應用實踐的垃圾回收器。

HotSpot垃圾回收器

Serial收集器

Serial:新生代:標記-複製演算法
Serial Old:老年代:標記-整理演算法
HotSpot虛擬機運行在客戶端模式下的預設新生代收集器
簡單高效、記憶體消耗最小

ParNew收集器

ParNew:新生代:標記-複製演算法
Serial Old:老年代:標記-整理演算法
激活CMS後,預設的新生代收集器
Serial的多線程版本,預設開啟的線程數與CPU核心數相同

Parallel Scavenge搜集器

標記-複製演算法,與ParNew相似
關註點在於達成可控制的吞吐量(吞吐量=用戶代碼運行時間/總時間;總時間=用戶代碼運行時間+垃圾回收時間)

參數說明

  • -XXMaxGCPauseMillis更關註停頓時間
    一個大於0的毫秒數,儘量使回收時間不超過這個值
    實現原理:犧牲吞吐量和新生代空間獲取,小記憶體新生代空間的回收速度一定由於高記憶體速度,但是回收頻率也會增加

  • -XXGCTimeRatio更關註吞吐量
    0到100之間的整數,代表垃圾回收時間占總時間的比率,相當於吞吐量的倒數

  • -UserAdaptiveSizePolicy
    開關函數,激活後虛擬機會根據當前運行情況自動調整Eden與Survivor的記憶體比例、老年代記憶體大小等參數,已提供合適的停頓時間和最大吞吐量

Serial Old 收集器

serial 收集器的老年版本,標記-整理演算法
在CMS收集器併發失敗時的預備方案

Parallel Old 收集器

Parallel Scavenge 收集器的老年版本,標記-整理演算法
在註重吞吐量或處理器資源稀缺時使用

CMS收集器

獲取最短停頓時間的為目標,採用併發-清除演算法

  • 工作步驟
    1. 初始標記
      標記GC Roots能直接關聯的對象,速度很快

    2. 併發標記
      從GC Roots直接關聯到的對象開始遍歷整個對象圖,耗時較長

    3. 重新標記
      修正併發標記期間,因用戶繼續運作導致標記產生變動的部分對象的標記記錄

    4. 併發清除
      清除掉標記的已死亡的對象

整個過程中,併發標記和併發清除耗時最久

  • 關鍵問題
    1. 併發過程中會占用部分資源
      當處理器核心數大於4時,預設回收線程數不超過25%(處理器核心數+3)/4
      但是當處理器核心數小於4時,用戶線程執行速度會大幅降低

    2. “浮動垃圾”與併發失敗
      與用戶程式運行併發運行就必然產生新的垃圾只有等下一次回收時才清理,這部分垃圾稱為“浮動垃圾”,所以需要給用戶線程預留足夠空間。因此,CMS不能等老年代滿了才進行收集,必須預留一部分作為併發時使用。如果CMS運行期間預留的記憶體無法滿足程式分配新對象的需求,就會出現“併發失敗”,這時候需要STW,臨時啟用Serial Old收集器對老年代的垃圾進行收集

    3. 記憶體碎片
      基於標記-清除演算法必然產生記憶體碎片,導致大對象分配時出現記憶體不足進而觸發Full GC。CMS提供-XX:UseCMSCompactAtFullCollection開關參數(預設開啟),當不得不進行Full GC時進行記憶體碎片整合,即移動存活對象。會使得停頓時間延長

Garbage First 收集器

建立可預測的停頓時間模型,開創了面向局部收集的記憶體設計思路,基於Region的記憶體佈局形式。預設停頓時間為200毫秒

  • 基於Region的記憶體佈局
    把連續的Java堆記憶體劃分為多個大小相等的獨立空間,每個空間都可以扮演Eden、Survivor空間或者老年代空間,其中Humongous區域轉為收集大對象(大小超過了一個Region的對象,Region的大小可通過參數調整),G1大多會把Humongous當做老年代看待。收集器可以根據不同的角色採取不同的收集策略。

  • 局部收集思想
    Region作為每次回收的最小記憶體單位,每次收集到的空間都是Region的整倍數,G1會跟蹤Region堆積的“價值”大小(回收所獲空間/回收所需時間的經驗值),再後臺維護一個優先順序列表,優先回收價值大的Region

  • 工作步驟
    1. 初始標記
      標記GC Roots能直接關聯的對象,並修改TAMS指針的值,是藉助Minor GC完成,所以不會造成額外的時間成本。
    2. 併發標記
      從GC Roots開始對堆中對象進行可達性分析,可併發執行,掃描完成時重新處理SATB記錄的引用變動
    3. 最終標記
      處理併發標記時的發生變動的對象,STW,併發完成
    4. 篩選回收
      更新Region的統計數據,根據用戶期望的停頓時間結合回收價值,確定需要回收Region集合。把需要回收的Region中存活的對象複製到空Region中,再清理需要回收的全部Region區域。
  • 關鍵問題
    1. 跨Region引用的處理辦法
      每個Region都維護一張自己的記憶集,記錄別的Region指向自己的指針,並標記這些指針在哪些卡頁範圍之內。其存儲結構本質上是一種哈希表,key是別的Region的起始地址,value是一個集合,存儲卡表的索引號。G1要耗費大越10%到20%的額外記憶體來維持收集器的工作。

    2. 併發干擾問題
      CMS在併發標記時採用增量更新的演算法實現,而G1則通過原始快照(SATB)演算法實現。此外,G1在回收過程中創建新對象的記憶體分配上也做了改動,G1為每個Region設計了兩個名為TAMS(Top At Mark Start)的指針,併發標記中新分配的對象都要在這兩個指針位置以上。G1收集器預設這部分對象是隱式標記過的,預設為存活

    3. 可靠地停頓預測
      -XX:MaxGCPauseMillis參數指用戶期望的停頓時間,具體實現是以“衰減均值”為理論基礎:在垃圾回收過程中,會記錄每個Region的回收耗時、記憶集中里的臟卡數量等各個可測量的步驟所花費的成本。“衰減均值”更能體現“最近”一段時間的平均狀態,更能在當下使回收不超過預期。(有點活在當下的感覺)

  • G1與CMS
    • 優點:
      可以指定最大停頓時間、分Region的記憶體佈局、按收益動態回收、不會產生記憶體碎片、回收完成後可提供規整的可用記憶體
    • 缺點:
      記憶體占用、程式執行的額外負載都較高
      G1的卡表更為複雜;運行負載方面,CMS使用寫後屏障來更細維護卡表,而G1為了實現原始搜索(SATB)快照演算法,還需要寫前屏障來跟蹤併發時的指針變化情況,G1能減少併發標記和重新標記的消耗,避免像CMS那樣在最終標記階段停頓時間過長。CMS直接同步處理,而G1非同步處理
  • 總結
    小記憶體上使用CMS有優勢,而大記憶體狀態下使用G1有更多優勢,而Java堆記憶體容量平衡點大約在6-8GB之間(經驗數據)

低延遲垃圾收集器

Shenandoah 收集器

ZGC 收集器


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 總是把這兩個當作同一個模式,但其實是不太一樣的,現在重溫一下。 觀察者模式 觀察者直接訂閱目標,當目標觸發事件時,通知觀察者進行更新 簡單實現 js class Observer { constructor(name) { this.name = name; } update() { console ...
  • 基礎頁面 為了演示如何通過 JavaScript 來創建 HTML 動畫,我們將使用一張簡單的網頁: 實例 創建動畫容器 所有動畫都應該與容器元素關聯。 實例 <div id ="container"> <div id ="animate">我的動畫在這裡。</div> </div> 創建動畫容器 ...
  • // 求最大值 <script> var arr = [10,35,765,21345,678,89]; var max = arr [0]; for (var i=0;i< arr.length;i++) { if (max<arr[i]){ max = arr [i]; } } console. ...
  • 時隔多久,我又回來寫博客了,最近忙於兩個課設,五周,搞得頭髮都不知道掉了多少根了,還沒成為程式員就開始掉了,等我成為一名程式員的時候豈不是要禿頭了,IT界的人會不會幫我當成大佬了,哈哈哈哈,希望我以後也可以成為一名IT界的大佬,雖然有點不現實,但是夢想還是要有的,萬一不經意間就實現了也說不定,加油~ ...
  • 1.DateTimeFormatter final修飾,線程安全,用於列印和解析日期-時間對象的格式化程式。 創建DateTimeFormatter: DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM ...
  • 本文旨在免費分享我所搜集到的Java學習資源,所有資源都是通過正規渠道獲取,不存在侵權。現在整理分享給有所需要的人。 希望對你們有所幫助!有新增資源我會更新的~大家有好的資源也希望分享,大家互幫互助共同進步! 鏈接有失效的請聯繫我更新,歡迎加入分享資源,分享交流技術知識,謝謝! 歡迎大家點贊轉載,轉 ...
  • Maven是個很好用的依賴管理工具,但是再好的東西也不是完美的。 ...
  • 近期的flink作業中,需要對上傳的日誌數據進行大量的校驗。 校驗規則大多比較簡單,僅為字元串長度,數組長度,數據的最大值和最小值,非空判斷等。然而不想寫諸多校驗代碼,容易導致代碼又醜又繁瑣。聯想SpringBoot項目中的參數校驗,於是想著在純maven的項目中引入校驗。 引入依賴 SpringB ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...