3 垃圾收集演算法

来源:https://www.cnblogs.com/knowledgeispower/archive/2022/09/20/16712317.html
-Advertisement-
Play Games

1 垃圾收集三件事 哪些記憶體需要回收:死去的對象需要回收 什麼時候回收 如何回收 按照jvm記憶體區域劃分原則:程式計數器、虛擬機棧、本地方法棧3個區域的記憶體隨線程創建而劃分,因此線程結束時,記憶體也自動釋放。 本章節分析的是Java堆和方法區的記憶體管理策略 1、虛擬機棧、本地方法棧,棧中的棧幀隨著方法 ...


目錄

1 垃圾收集三件事

  1. 哪些記憶體需要回收:死去的對象需要回收
  2. 什麼時候回收
  3. 如何回收

按照jvm記憶體區域劃分原則:程式計數器、虛擬機棧、本地方法棧3個區域的記憶體隨線程創建而劃分,因此線程結束時,記憶體也自動釋放。
本章節分析的是Java堆和方法區的記憶體管理策略

1、虛擬機棧、本地方法棧,棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧操作。
  每一個棧幀中分配多少記憶體基 本上是在類結構確定下來時就已知的(儘管在運行期會由即時編譯器進行一些優化,但在基於概念模 型的討論里,大體上可以認為是編譯期可知的),因此這幾個區域的記憶體分配和回收都具備確定性。
2、堆和方法區這兩個區域則有著很顯著的不確定性:一個介面的多個實現類需要的記憶體可能會不一樣,一個方法所執行的不同條件分支所需要的記憶體也可能不一樣,
只有處於運行期間,我們才能知道程式究竟會創建哪些對象,創建多少個對象,這部分記憶體的分配和回收是動態的。

2 對象存活判定演算法

回收堆,也就是回收對象,判斷對象是否需要回收,也就是判斷對象是否死亡,有兩種策略:引用計數演算法和可達性分析演算法

2.1 引用計數演算法

在對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的對象就是不可能再被使用的

缺點

  1. 當對象存在相互引用時,該判斷方法失效
  2. 當前較少java虛擬機應用該演算法

關於引用的說明

分類 定義 垃圾回收
強引用 程式代碼之中普遍存在的引用賦值 不回收
軟引用 一些還有用,但非必須的對象。SoftReference修飾 將要發生溢出時,才會被回收
弱引用 強度比軟引用 弱一點。 WeakReference 修飾 垃圾收集器啟動,就會被回收,而不管是否發生溢出
虛引用 目的只是為了能在這個對象被收集器回收時收到一個系統通知 垃圾收集器啟動,就會被回收 【設置虛引用的目的僅是為了在對象被回收時收到一個通知】

2.2 可達性分析演算法

通過GC Root節點,根據引用關係向下遍歷,當存在對象不在引用連上,則該對象可能不在被引用。
註意:當前下,根節點選舉,還是需要暫停所有用戶線程,以便保證快照一致性

在Java技術體系裡面,固定可作為GC Roots的對象包括以下幾種:

  • 虛擬機棧中引用的對象,譬如各個線程被調用的方法堆棧中使用到的參數、局部變數、臨時變數等
  • 方法區中類靜態屬性引用的對象,譬如Java類的引用類型靜態變數
  • 方法區中常量引用的對象,譬如字元串常量池裡的引用
  • 本地方法棧中Native方法引用的對象
  • 所有被同步鎖(synchronized)持有的對象

2.2.1 不可達對象的後置處理

當對象被判斷為不可達對象後,它仍有可能不被回收:調用了finalize()方法並且在方法里調用其它存活對象
因此,不可達對象在第一次標誌後,還會有一個執行判斷過程:

  1. 當對象被判定為不可達對象後,進行第一次標記。
  2. 對已經被標記的對象篩選出來,判斷是否需要執行finalize()方法,需要就放到執行隊列裡面。
  3. 在finalize(),如果產生對存活對象的引用,jvm會將該不可達對象移除待回收的集合。

過程如下所示:

關於finalize()方法

  1. 它的運行代價高昂,不確定性大,無法保證各個對象的調用順序,因此不推薦使用
  2. finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好

2.3 方法區回收判定

  1. 方法區的回收條件比較苛刻,成本高,《Java虛擬機規範》不要求實現方法區域的垃圾回收
  2. HotSpot虛擬機中的元空間或者永久代是沒有垃圾收集行為
  3. 方法區的垃圾收集主要回收兩部分內容:廢棄的常量不再使用的類型

一、判斷常量是否被廢棄

  1. 沒有任何對象引用常量池中的這個常量
  2. 虛擬機中也沒有其他地方引用這個常量

二、判斷類型是否不再使用

  1. 類所有的實例都已經被回收,也就是Java堆中不存在該類及其任何派生子類的實例
  2. 載入該類的類載入器已經被回收
  3. 類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法
Java虛擬機被允許對滿足上述三個條件的無用類進行回收,這裡說的僅僅是“被允許”,而並不是 和對象一樣,沒有引用了就必然會回收。
關於是否要對類型進行回收,HotSpot虛擬機提供了- Xnoclassgc參數進行控制,還可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX: +TraceClassUnLoading查看類載入和卸載信息

5 垃圾收集演算法介紹

5.1 分代收集理論

!!!重要重要重要

  1. 將堆記憶體按照區域劃分,存儲不同"年齡"對象。(即分為新生代老年代);
  2. 新生代對象可以轉到老年代去;
  3. 對於新生代,回收時只關註少量需要保留的對象;
  4. 對於老年代,使用低頻率來進行回收;
  5. 由於分區的出現,促使回收可以針對特定區域進行,或者不同的區域使用不同的回收演算法。
  6. 對於跨代對象,在新生代建立記憶表,存儲老年代哪些區域存在跨帶引用,在回收處理時,僅處理該區域的對象;

關於收集的補充說明
部分收集(Partial GC):指目標不是完整收集整個Java堆的垃圾收集,分為:

  • 老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。
  • 混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集
  • 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。

5.1.1 記憶集與卡表

  1. 記憶集的目的:解決跨帶引用帶來的可能要掃描整個老年代的問題
  2. 它是一個存儲在非收集區的指針集合的數據結構,元素指向收集區
  3. 卡表:記憶集精度到記憶體區域,稱為卡表;抽象為一個位元組數組
  4. 卡頁:卡表的一個元素:記憶體塊:存儲一個記憶體地址,根據指定頁碼的大小(2的N次冪位元組數),構成地址範圍;
  5. 一個卡頁的記憶體中通常包含不止一個對象,只要卡頁內有一個(或更多)對象的欄位存在著跨代 指針,那就將對應卡表的數組元素的值標識為1,稱為這個元素變臟(Dirty),沒有則標識為0。在垃 圾收集發生時,只要篩選出卡表中變髒的元素,就能輕易得出哪些卡頁記憶體塊中包含跨代指針,把它 們加入GC Roots中一併掃描。

5.2 標記-清除演算法

  1. 標記出所有待回收對象
  2. 對被標記對象進行清除
  3. 缺點:1、如果有大量待清除對象,則出現多次的標記-清除操作(我理解是既然被清除,則無需進行標記);2、產生記憶體碎片

5.3 標誌-複製演算法

  1. 將記憶體同等劃分2個區域,新對象都被放在其中一個區域A;
  2. 當該區域記憶體用完後,將活著的對象複製到另外一塊區域中B;
  3. 將已使用過半區進行回收清除;同時新對象只會出現在區域B中;
  4. 優點:不會產生記憶體碎片(存活對象被放在一起,待回收對象被整塊清除)
  5. 缺點:1、複製會產生開銷;2、記憶體浪費:可用記憶體只剩一半

5.4 優化後的標誌-複製演算法

  1. 將新生代跨分成三個區域:一大:Eden,兩小Survivor。分別占位10:8 10:1 10:1,新產生的對象隨機進入使用Eden和其中一塊正在使用的Survivor
  2. 發生垃圾搜集時,將Eden和Survivor中仍然存活的對象一次性複製到另外一塊Survivor空間上,然後直接清理掉Eden和已用過的那塊Survivor
  3. 當Survivor不足以存放存活對象時,轉入到入老年代。如果對象經過經過18GC後,還存活,那麼也會轉入到老年代

過程如圖所示:

5.5 標記-整理演算法

  1. 其中的標記過程仍然與“標記-清除”演算法一樣;
  2. 不對可回收對象直接回收,而是讓所有存活的對象都向記憶體空間一端移動,然後直接清理掉邊界以外的記憶體
  3. 缺點:對象被移動,需要更新其引用,需要停止所有運行線程

過程如圖所示:

6 根節點枚舉

6.1 關於根節點及其枚舉

  1. 可以作為根節點的數據集中在:方法區【量池、靜態變數】、虛擬機棧:【本地變數表】
  2. 根節點枚舉,需要暫停用戶線程
  3. 另外,查找引用鏈過程已經實現了跟用戶線程併發

6.2 oopMap數據結構

oopMap是什麼?

  1. oopMap是一個數據結構,它存儲的內容可以作為根節點。
  2. jvm執行某些位元組碼指令時,會創建該數據結構

為什麼需要oopMap?

  1. 優點:通過oopMap,jvm可以直接找到對象的引用, jvm不需要一個不漏地檢查完所有 執行上下文和全局的引用位置,從而快速地完成根節點枚舉
  2. 缺點:引起OopMap內容變化的指令非常多,如果為每一條指令都生成對應的oopMap,那將會需要大量的額外存儲空間

oopMap如何實現

  1. 類載入完成後,保存對象的屬性的偏移地址。【備註一】
  2. 即時編譯時,保存棧里對象的引用地址【備註二】

6.3 安全點

安全點是什麼

  1. 編譯生成位元組碼指令時,只針對特定的指令,才創建oopMap,我們把這些特定指令的地址稱為安全點。

為什麼需要安全點?

  1. 如果對所有的指令都生成oopMap,會耗費大量的空間;選擇在安全點位置創建oopMap,節省記憶體空間
  2. 有了安全點的設定,也就決定了用戶程式執行時並非在代碼指令流的任意位置都能夠停頓下來開始垃圾收集,而是強制要求必須執行到達安全點後才能夠暫停。

安全點如何實現?

  1. 安全點位置的選取在具備讓程式長時間執行的復用型指令,例如方法調用、迴圈跳轉、異常跳轉 ,只有具有這些功能的指令才會產生安全點;
  2. JVM使用主動式中斷方案,讓線程暫停執行,來響應GC事件。

關於主動式中斷

  1. 當垃圾收集需要中斷線程的時候,不直接對線程暫停,僅設置一個標誌位,各個線程執行時輪詢這個標誌,一旦發現中斷標誌為真時就自己在最近的安全點上主動中斷掛起
另一種方案是搶先式中斷:【主流虛擬機不使用該方案】
搶先式中斷不需要線程的執行代碼 主動去配合,在垃圾收集發生時,系統首先把所有用戶線程全部中斷,如果發現有用戶線程中斷的地 方不在安全點上,就恢復這條線程執行,讓它一會再重新中斷,直到跑到安全點上

6.4 安全區域

安全區域是什麼?

  1. 在該代碼片中,對象引用關係不會發生變化,那麼這塊代碼(或者位元組碼指令片段)就是安全區域

為什麼需要安全區域?

  1. 線程沒有分配cpu時間時(處於Sleep狀或Blocked狀態)無法響應虛擬機的中斷請求,不能再走到安全的地方去中斷掛起自己,安全點的設置就不起效,因此需要安全區域。
  2. 在安全區域進行垃圾收集是安全的。

安全區域對線程和回收器的影響

  1. 當用戶線程執行到安全區域裡面的代碼時,首先會標識自己已經進入了安全區域。
  2. 虛擬機要發起垃圾收集時,將不會給處於安全區域的線程打上暫停標誌【對應主動式中斷的打標識】
  3. 當線程要離開安全區域時,它要檢查虛擬機是否已經完成了根節點枚舉:
    1. 完成:線程就當作沒事發生過,繼續執行
    2. 未完成:線程一直等待,直到收到可以離開安全區域的信號為止。

用一張圖來描述:

6.5 oopMap、安全點、安全區域對比總結

用一張圖來總結:

7 可達性遍歷的併發分析

未完成待續


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

-Advertisement-
Play Games
更多相關文章
  • Java面向對象 1.類和對象 1.1 類和對象的概念: 類是抽象的集合,對象是具體的實例。 類可以想象為製作蛋糕的模具,對象就是做出來的蛋糕。 類中包含屬性(欄位)和方法(操作) 1.2 類的定義: Class ClassName { 屬性1 屬性2 ··· 構造器1 構造器2(如果不寫,系統會默 ...
  • 寫程式之前要瞭解兩個概念 1.什麼是進程 2.什麼是線程 搞清楚這兩個概念之後 才能寫好一個合適而不會太抽象的程式 對進程和線程的理解見鏈接: https://blog.csdn.net/new_teacher/article/details/51469241 https://www.cnblogs ...
  • form表單內容序列化 form表單自帶兩種方法serialize()方法和serialize()方法 1.serialize()方法 描述:序列化表單內容為字元串(不包括文件),用於Ajax請求。 格式:var data = $('#form').serialize(); 2.serializeA ...
  • 我的gRPC之旅。使用gRPC一元通信模式和雙向流通信模式寫一個簡單的控制台聊天室。實現創建用戶和實時聊天兩個功能,不考慮高性能。複習了記憶體同步訪問Sync包的使用。用切片緩存聊天記錄,新用戶可以同步聊天記錄。 ...
  • Java基礎知識 Java的三種版本 JavaSE :標準版,主要用於開發桌面程式,控制台開發等等 JavaME:嵌入式開發,主要用於開發手機,小家電等等,目前使用的比較少 JavaEE:企業級開發,主要用於web端開發,伺服器開發等等,是使用十分廣泛的,學好這部分就要學好JavaSE JDK、JR ...
  • 來源:liuchenyang0515.blog.csdn.net/article/details/109263510 對稱加密 兩邊用同一個密鑰來加解密。 A把明文通過某一演算法加密之後得到密文,然後把密文發送給B,B接收到密文之後用相同的密鑰執行相同的演算法去解密。X沒有密鑰,即使竊取到密文也無法竊聽 ...
  • 多用戶即時通訊系統01 1.項目開發流程 2.需求分析 用戶登錄 拉取線上用戶列表 無異常退出(包括客戶端和服務端) 私聊 群聊 發文件 伺服器推送新聞/廣播 3.設計階段 3.1界面設計 用戶登錄: 拉取線上用戶列表: 私聊: 群聊: 發文件: 文件伺服器推送新聞: 3.2通訊系統整體設計 總結: ...
  • 實時展示用戶上傳的頭像 總體思路 """ 1.首先需要給對應的上傳頭像input框綁定一個文本域變化事件 (當檢測到用戶對該文件框上傳了頭像就會觸發一系列操作) 2.再生成一個文件閱讀器對象 3.再獲取用戶上傳的文件頭像 4.把用戶上傳的文件頭像交給文件閱讀器對象FileReader讀取 5.利用文 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...