JVM垃圾回收相關算法

目錄

一、前言

二、標記階段:引用計數算法

三、標記階段:可達性分析算法

(一)基本思路

(二)GC Roots對象

四、對象的finalization機制

五、MAT與JProfiler的GC Roots溯源

六、清除階段:標記-清除算法Mark-Sweep

七、清除階段:復制算法Copying

八、清除階段:標記-整理算法Mark-Compact

九、對比三種算法

十、分代收集算法

十一、增量收集算法、分區算法


一、前言

對于Java開發人員而言,自動內存管理就像是一個黑匣子,如果過度依賴于“自動”,那么將會是一場災難,最嚴重的莫過于弱化Java人員在程序出現內存溢出時定位問題和解決問題的能力。

所以了解JVM的自動內存分配和內存回收原理顯得非常重要,在遇見OOM的時候才能快速的根據錯誤異常日志定位問題和解決問題。

當需要排查各種內存溢出,內存泄漏問題時,當垃圾收集成為系統達到更高并發量的瓶頸時,我們必須對這些“自動化”的技術實施必要的監控和調節。

垃圾回收器可以對年輕代和老年代進行回收,甚至是全堆和方法區的回收,其中,Java堆時垃圾收集器的工作重點

從次數上講:頻繁手機Young區,較少收集Old區,基本不動方法區

那么什么是垃圾?

垃圾是指運行程序中沒有任何指針指向的對象,這個對象就是需要被回收的垃圾

二、標記階段:引用計數算法

對每個對象保存一個整型的引用計數器屬性,用于記錄被對象引用的情況。被對象引用了就+1,引用失效就-1,0表示不可能再被使用,可進行回收

優點:實現簡單,垃圾便于辨識,判斷效率高,回收沒有延遲性

缺點

  • 需要單獨的字段存儲計數器,增加了存儲空間的開銷
  • 每次賦值需要更新計數器,伴隨加減法操作,增加了時間開銷
  • 無法處理循環引用的情況,致命缺陷,導致JAVA的垃圾回收器中沒有使用這類算法

引用計數算法,是很多語言的資源回收選擇,例如python,它更是同時支持引用計數和垃圾回收機制,python如何解決循環引用

  • 手動解除
  • 使用弱引用,weakref,python提供的標準庫,旨在解決循環引用

三、標記階段:可達性分析算法

(一)基本思路

可達性分析算法是以根對象(GCRoots)為起始點,按照從上到下的方式搜索被根對象集合所連接的目標對象是否可達

使用可達性分析算法后,內存中存活的對象都被被根對象集合直接或間接連接著,搜索所走過的路徑稱為引用鏈。如果目標對象沒有任何引用鏈相連,則是不可達的,意味著該對象已經死亡,可以標記為垃圾對象。

在可達性分析算法中,只有能夠被根對象集合直接或者間接連接的對象才是存活的對象

(二)GC Roots對象

根對象(GC Roots)包括以下幾種:

  1. 虛擬機棧中引用的對象,比如:各個線程被調用的方法中使用到的參數、局部變量
  2. 本地方法棧內JNI,引用的對象
  3. 方法區中靜態屬性引用的對象,比如:java類的引用類型靜態變量
  4. 方法區中常量引用的對象,比如:字符串常量池里的引用
  5. 所有被同步鎖synchronized持有的對象
  6. Java虛擬機內部的引用,比如:基本數據類型對應的class對象,一些常駐的異常對象,如nullpointerException,OOMerror,系統類加載器
  7. 反映java虛擬機內部情況的JMXBean,JVMTI中注冊的回調,本地代碼緩存等
  8. ?除了固定的GC Roots集合之外,根據用戶選擇的垃圾收集器以及當前回收的內存區域不同,還可以有其他對象臨時性的加入,共同構成完整GCRoots集合,比如分代收集和局部回收(比如專門針對新生代的回收,那么其他非新生代 對象比如老年代也應該考慮作為root對象。因為新生代中的某些對象有可能被老年代的對象引用。)

如果需要使用可達性分析算法來判斷內存是否可回收,那么分析工作必須在一個能保障一致性的快照中進行。這點不滿足的話,分析結果的準確性就無法保證。

這也是GC進行時必須STW的一個重要原因,即使是號稱幾乎不會發生停頓的CMS收集器中,枚舉根節點也是必須要停頓的。

四、對象的finalization機制

Java語言提供了對象終止finaliztion機制來允許開發人員提供對象被銷毀之前的自定義處理邏輯。當垃圾回收器發現沒有引用指向一個對象,即垃圾回收此對象之前,總會先調用這個對象的finalize()方法。

finalize()方法允許在子類中被重寫,用于在對象被回收時進行資源釋放,通常在這個方法中進行一些資源釋放和清理的工作,比如關閉文件,套接字和數據庫鏈接等

對象的三種狀態:

  • 可觸及的:從根節點開始,可以到達這個對象
  • 可復活的:對象的所有引用都被釋放了,但是對象有可能在finalize()中復活
  • 不可觸及的:對象的finalize()被調用,并且沒有復活,那么就會進入不可觸及狀態。不可觸及的對象不可能被復活,因為finalize()只會被調用一次
  • 只有對象再不可觸及時才可以被回收

判斷一個對象ObjA是否可以被回收,至少需要經歷兩次標記過程

  • 1、如果對象到GCRoots沒有引用鏈,則進行第一次標記
  • 2、進行篩選,判斷此對象是否有必要執行finalize()方法
    • 如果對象A沒有重寫finalize方法,或者finalize方法已經被虛擬機調用過,則虛擬機視為沒有必要執行,對象A被判定為不可觸及的
    • 如果對象A重寫finalize()方法,且還未執行過,那么A會被插入到F-queue隊列中,有一個虛擬機自動創建的,低優先級的Finalizer線程觸發其finalize()方法執行
    • finalize方法是對象逃脫死亡的最后機會,稍后GC會對F-queue隊列中的對象進行第二次標記,如果A在finalize方法中與引用鏈上的任何一個對象建立了聯系,那么在第二次標記時,A會被移除即將回收集合。之后,對象會再次出現沒有引用存在的情況下,finalize方法不會再被調用,對象直接變為不可觸及狀態

public class CanRelliveObj {public static CanRelliveObj obj;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("調用當前類重寫 finalize 方法");obj = this;}public static void main(String[] args) {try {// 先創建一個對象,分配下內存,不然重來沒出現過何來回收一說obj = new CanRelliveObj();obj = null;System.gc(); // 回收時會調用對象的finalize方法,第一次調用成功拯救自己System.out.println("第一次 gc");// 因為Finalizer線程優先級很低,暫停2s,等待它Thread.sleep(2000);if(obj == null) {System.out.println("obj dead");} else {System.out.println("obj is still alive");}System.out.println("第二次 gc");obj = null;System.gc(); // 此時回收對象發現finalize方法已經被調用,所以直接進行回收if(obj == null) {System.out.println("obj dead");} else {System.out.println("obj is still alive");}} catch (Exception e) {}}
}

五、MAT與JProfiler的GC Roots溯源

MAT是Memory Analyzer的簡稱,是一款功能強大的Java堆內存分析器。用于查找內存泄露以及查看內存消耗情況,基于Eclipse開發的一款免費性能分析工具

可以用于分析GC Roots對象

六、清除階段:標記-清除算法Mark-Sweep

標記:從引用根節點開始遍歷,標記所有被引用的對象,一般是在對象Header中記錄為可達對象

注意標記引用對象,不是垃圾對象

清除:對堆內存從頭到尾進行線性的遍歷,如果發現某個對象在其Header中沒有標記為可達對象,則將其回收

缺點

  • 效率不算高
  • 在GC的時候,需要停止整個應用程序,導致用戶體驗差。
  • 這種方式清理出來的空閑內存不連續,產生內存碎片需要維護一個空閑列表

何為清除?

所謂的清除并不是真的置空,而是把需要清除的對象地址保存在空閑的地址列表里,下次有新對象需要加載時,判斷垃圾的位置空間是否夠,如果夠就存放。

總結:第一次遍歷標記可達對象,第二次遍歷清除未標記對象。清除實際上是將未標記對象加入空閑列表,下次有新對象產生,判斷空閑列表中垃圾的位置放不放的下,放得下就覆蓋。

七、清除階段:復制算法Copying

為了解決標記-清除算法在垃圾收集效率方面的缺陷——產生內存碎片。1963年出現了復制(Copying)算法

原理:將或者的內存空間分為兩塊,每次使用其中一塊。在垃圾回收時,將正在使用的內存中的存活的對象復制到未被使用的內存塊中,之后清除正在使用的內存塊中的所有的對象,交換兩個內存的角色,最后完成垃圾回收

優點

  • 沒有標記和清除的過程,實現簡單高效
  • 復制過去以后的保證空間的連續性,不會出現碎片的問題

缺點

  • 需要兩倍的內存空間
  • 對于G1這種拆分為大量region的GC,復制而不是移動,意味著GC需要維護region之間的引用關系(就像對象的兩種),不管是內存占用或者時間開銷也不小。

注意:如果系統中的垃圾對象很多,需要復制的存活對象數量并不會太大,或者非常低使用復制算法效率才會高。想一想,如果每次復制都發現垃圾對象很少,基本每次復制都是全部移動,那效率肯定很低。

應用場景:

在新生代,對常規應用的垃圾回收,一次通常可以回收70%-90%的內存空間。回收性價比很高,所以現在的商業虛擬機都是用這種手機算法回收新生代。?(記得from區和to區嗎,為什么總有一個區是空的,現在聯系起來了。使用的就是是復制算法

八、清除階段:標記-整理算法Mark-Compact

標記-整理又叫標記-壓縮算法。

背景:復制算法的高效性是建議在存活對象少,垃圾對象多的前提下的。適用于新生代,而老年代大部分對象都是存活對象,所以并不適用,否則復制成本較高。因此,基于老年代垃圾回收的特性,需要使用其他算法。

標記-清除算法可以應用在老年代中,但是該算法不僅執行效率低下,而且執行完內存回收后還會產生內存碎片。所以JVM的設計者在此基礎之上進行改進,標記-整理垃圾收集算法誕生了。

執行過程

  1. 第一個階段和標記清除算法一樣,從根節點開始標記所有被引用的對象
  2. 第二階段將所有的存貨對象壓縮在內存的一端,按照順序排放,之后清理邊界外所有的空間
  3. 最終效果等同于標記清除算法執行完成后,再進行一次內存碎片整理。

與標記清除算法本質區別,標記清除算法是非移動式的算法,標記壓縮是移動式的

優點

  • 消除了標記-清除算法內存區域分散的缺點,
  • 消除了復制算法中,內存減半的高額代價

缺點

  • 從效率上來講,標記整理算法要低于復制算法
  • 移動對象的同時,如果對象被其他對象引用,則還需要調整引用的地址
  • 移動的過程中,需要全程暫停用戶應用程序,即STW

九、對比三種算法

效率上來說,復制算法是最快的(因為不像標記-清除和標記整理那樣需要標記,還有整理),但是浪費了太多的內存。?

而標記-整理算法相對來說更加平滑一些,但是效率上不太行,比復制算法多了一個標記的階段,比標記-清除多了一個整理內存的階段。

想到了一個問題:復制算法不標記怎么知道一個對象是否存活,是否需要進行復制?

即:復制算法不用進行標記嗎?

查閱相關資料后,明白了。復制算法沒有像標記-清除和標記-整理兩個方法一樣有單獨的標記過程。因為復制gc只需要把“活”的對象拷貝到survivor,還要mark什么呢?至于怎么判斷是“活”的,gc roots以下的不都是“活”的?復制算法是從gc roots開始,遇到活對象就復制走了。gc roots找可達對象的過程結束就復制完了。不像標記算法那樣,對于一個對象是否需要回收要滿足兩個條件:① 對象不可達;②沒必要執行finalize方法。

java gc中為什么復制算法比標記整理算法快? - 簡書

十、分代收集算法

不同生命周期的對象可以采取不同額收集方式,以便提高回收效率

幾乎所有的GC都采用分代收集算法執行垃圾回收的

HotSpot中

  • 年輕代:生命周期短,存活率低,回收頻繁
  • 老年代:區域較大,生命周期長,存活率高,回收不及年輕代頻繁

十一、增量收集算法、分區算法

(一)增量收集算法思想

每次垃圾收集線程只收集一小片區域的內存空間,接著切換到應用程序線程,依次反復,直到垃圾收集完成

通過對線程間沖突的妥善管理,允許垃圾收集線程以分階段的方式完成標記、清理或復制工作

缺點:線程和上下文切換導致系統吞吐量的下降

(二)分區算法

為了控制GC產生的停頓時間,將一塊大的內存區域分割成多個小塊,根據目標的停頓時間,每次合理的回收若干個小區間,而不是整個堆空間,從而減少一次GC所產生的時間

分代算法是將對象按照生命周期長短劃分為兩個部分,分區算法是將整個堆劃分為連續的不同的小區間

每一個小區間都獨立使用,獨立回收,這種算法的好處是可以控制一次回收多少個小區間

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/161983.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/161983.shtml
英文地址,請注明出處:http://en.pswp.cn/news/161983.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于PCA算法的點云平面擬合

平面擬合 1、平面擬合2、參考文獻3、相關代碼 1、平面擬合 PCA 是一種數學變換的方法,利用降維的思想在變換中保持變量的總方差不變,將給定的一組變量線性變換為另一組不相關的變量,并且使變換后的第一變量的方差最大,即第一主成分…

OpenCV將兩張圖片拼接成一張圖片

OpenCV將兩張圖片拼接成一張圖片 示例代碼1示例代碼2 可以用opencv或者numpy的拼接函數,直接將兩張圖拼接到一起,很簡單方便,參考代碼2,推薦此方式。新建圖片,將兩張圖片的像素值填充到新圖片對應位置上即可&#xff0…

leetcode 32最長有效括號 34在排序數組中查找元素的第一個和最后一個位置

32. 最長有效括號 給你一個只包含 ( 和 ) 的字符串,找出最長有效(格式正確且連續)括號子串的長度。 示例 1: 輸入:s "(()" 輸出:2 解釋:最長有效括號子串是 "()" 示例 2&a…

python 二分查找函數應用——bisect_left(nums,target),bisect_right(nums,target)

bisect_left(nums,target),bisect_right(nums,target)是python內置的函數,可以便捷的幫我們完成一些有序序列的查找工作,現在將用三個樣例進行講解演示 前提注意事項: 導入函數模塊 待處理序列必須有序!!&#xff0…

淺談WPF之各種Template

前幾天寫了一篇文章【淺談WPF之控件模板和數據模板】,有粉絲反饋說這兩種模板容易弄混,不知道什么時候該用控件模塊,什么時候該用數據模板,以及template和itemtemplate之間的關系等,今天專門寫一篇文章,簡述…

26 - 原型模式與享元模式:提升系統性能的利器

原型模式和享元模式,前者是在創建多個實例時,對創建過程的性能進行調優;后者是用減少創建實例的方式,來調優系統性能。這么看,你會不會覺得兩個模式有點相互矛盾呢? 其實不然,它們的使用是分場…

TC397 EB MCAL開發從0開始系列 之 [15.1] Fee配置 - 雙扇區demo

一、Fee配置1、配置目標2、目標依賴2.1 硬件使用2.2 軟件使用2.3 新增模塊3、EB配置3.1 配置講解3.2 模塊配置3.2.1 MCU配置3.2.2 PORT配置3.2.3 Fls_17_Dmu配置3.2.4 Fee配置3.2.5 Irq配置3.2.6 ResourceM配置4、ADS代碼編寫及調試4.1 工程編譯4.2 測試結果4.3 測例源碼->

2023年學習Go語言是否值得?探索Go語言的魅力

關注公眾號【愛發白日夢的后端】分享技術干貨、讀書筆記、開源項目、實戰經驗、高效開發工具等,您的關注將是我的更新動力! 作為一門流行且不斷增長的編程語言,Go語言在2023年是否值得學習呢?讓我們來看看學習Go語言的好處以及為何…

Java使用Maven打包jar包的全部方式

1. spring-boot-maven-plugin插件&#xff08;在springboot項目中使用&#xff09; <plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals>…

1410.HTML 實體解析器

??題目來源&#xff1a; leetcode題目&#xff0c;網址&#xff1a;1410. HTML 實體解析器 - 力扣&#xff08;LeetCode&#xff09; 解題思路&#xff1a; 使用map存放特殊字符串及其應被替換為的字符串。然后遍歷字符串替換 map 中的字符串即可。 解題代碼&#xff1a; …

ubuntu 手動清理內存cache

/proc是一個虛擬文件系統&#xff0c;我們可以通過對它的讀寫操作來做為與kernel實體間進行通信的一種手段。也就是說可以通過修改/proc中的文件&#xff0c;來對當前kernel的行為做出調整。 那么我們可以通過調整/proc/sys/vm/drop_caches來釋放內存。操作如下&#xff1a; …

富士康轉移產線和中國手機海外設廠,中國手機出口減少超5億部

富士康和蘋果轉移生產線對中國手機制造造成了巨大的影響&#xff0c;除此之外&#xff0c;中國手機企業紛紛在海外設廠也在減少中國手機的出口&#xff0c;2022年中國的手機出口較高峰期減少了5.2億部。 手機是中國的大宗出口商品&#xff0c;不過公開的數據顯示2022年中國的手…

每日OJ題_算法_雙指針_力扣202. 快樂數

力扣202. 快樂數 202. 快樂數 - 力扣&#xff08;LeetCode&#xff09; 難度 簡單 編寫一個算法來判斷一個數 n 是不是快樂數。 「快樂數」 定義為&#xff1a; 對于一個正整數&#xff0c;每一次將該數替換為它每個位置上的數字的平方和。然后重復這個過程直到這個數變為…

RT-Thread 線程間同步【信號量、互斥量、事件集】

線程間同步 一、信號量1. 創建信號量2. 獲取信號量3. 釋放信號量4. 刪除信號量5. 代碼示例 二、互斥量1. 創建互斥量2. 獲取互斥量3. 釋放互斥量4. 刪除互斥量5. 代碼示例 三、事件集1. 創建事件集2. 發送事件3. 接收事件4. 刪除事件集5. 代碼示例 簡單來說&#xff0c;同步就是…

PDF轉成圖片

使用開源庫Apache PDFBox將PDF轉換為圖片 依賴 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.4</version> </dependency> <dependency><groupId>org.apache…

DockerHub 無法訪問 - 解決辦法

背景 DockerHub 鏡像倉庫地址 https://hub.docker.com/ 突然就無法訪問了,且截至今日(2023/11)還無法訪問。 這對我們來說,還是有一些影響的: ● 雖然 DockerHub 頁面無法訪問,但是還是可以下載鏡像的,只是比較慢而已 ● 沒法通過界面查詢相關鏡像,或者維護相關鏡像了…

JAVA 使用stream流將List中的對象某一屬性創建新的List

JAVA 使用stream流將List中的對象某一屬性創建新的List 1.stream流介紹 Java Stream是Java 8引入的一種新機制&#xff0c;它可以讓我們以聲明式方式操作集合數據&#xff0c;提供了更加簡潔、優雅的集合處理方式。Stream是一個來自數據源的元素隊列&#xff0c;并支持聚合操…

【Rxjava詳解】(二) 操作符的妙用

文章目錄 接口變化操作符mapflatmapdebouncethrottleFirst()takeconcat RxJava 是一個基于 觀察者模式的異步編程庫&#xff0c;它提供了豐富的操作符來處理和轉換數據流。 操作符是 RxJava 的核心組成部分&#xff0c;它們提供了一種靈活、可組合的方式來處理數據流&#xf…

C++二分算法:得到子序列的最少操作次數

本文涉及的基礎知識點 二分查找算法合集 題目 給你一個數組 target &#xff0c;包含若干 互不相同 的整數&#xff0c;以及另一個整數數組 arr &#xff0c;arr 可能 包含重復元素。 每一次操作中&#xff0c;你可以在 arr 的任意位置插入任一整數。比方說&#xff0c;如果…