1. 程式人生 > >圖解JVM垃圾回收演算法

圖解JVM垃圾回收演算法

1 簡單介紹下----->垃圾回收概念

GC中的垃圾,指的是存在於記憶體中的、不會再被使用的物件。而垃圾回收就是把那些不再被使用的物件進行清除,收回佔用的記憶體空間。如果不及時對記憶體中的垃圾進行清理,那麼這些垃圾物件所佔的記憶體空間會一直保留到應用程式結束,被保留的空間無法被其他物件使用。如果大量不會被使用的物件一致佔著空間不放,如果應用程式需要記憶體空間,沒有多餘的記憶體空間供其使用的話,就會導致記憶體溢位。因此,對記憶體空間的管理來說,識別和清理垃圾物件是至關重要的。

但是怎麼識別一個物件是否存活??也就是可達的?依據什麼策略來判斷一個物件的可達性??

在java中使用根搜尋演算法

(GC Roots Tracing)判斷一個物件是否是可達的。演算法的基本思路就是通過一系列的根節點"GC Roots"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有引用鏈相連時,則說明這個物件是不可達的。就會被判斷為可被回收的物件。

一般什麼樣的物件可以作為 GCRoots呢?

在java中以下幾種物件可以作為GCRoots:

1)虛擬機器棧(棧幀中的本地變量表)中引用的物件

2)方法區中的類靜態屬性引用的物件。

3)方法區中的常量引用的物件

4)本地方法棧中JNI(通常說的Native方法)引用的物件

2 重點圖解介紹----->垃圾回收演算法

下面首先會先介紹演算法的主要思想,然後會使用圖解的方式直觀演算法的工作流程。。。。。。。。。

 重點掌握其演算法思想,以及其演算法的優缺點和適用場景。。。。

(1) 引用計數法

引用計數法是最經典的一種垃圾回收演算法。其實現很簡單,對於一個A物件,只要有任何一個物件引用了A,則A的引用計算器就加1,當引用失效時,引用計數器減1.只要A的引用計數器值為0,則物件A就不可能再被使用。

雖然其思想實現都很簡單(為每一個物件配備一個整型的計數器),但是該演算法卻存在兩個嚴重的問題

1)  無法處理迴圈引用的問題,因此在Java的垃圾回收器中,沒有使用該演算法

2)  引用計數器要求在每次因引用產生和消除的時候,需要伴隨一個加法操作和減法操作,對系統性能會有一定的影響。

一個簡單的迴圈引用問題描述:

物件A和物件B,物件A中含有物件B的引用,物件B中含有物件A的引用。此時物件A和B的引用計數器都不為0,但是系統中卻不存在任何第三個物件引用A和B。也就是說A和B是應該被回收的垃圾物件,但由於垃圾物件間的互相引用使得垃圾回收器無法識別,從而引起記憶體洩漏(由於某種原因不能回收垃圾物件佔用的記憶體空間)。

如下圖:不可達物件出現迴圈引用,它的引用計數器不為0,

 

注意由於引用計數器演算法存在迴圈引用以及效能的問題,java虛擬機器並未使用此演算法作為垃圾回收演算法

【可達物件】:通過根物件的進行引用搜索,最終可以到達的物件。

【不可達物件】:通過根物件進行引用搜索,最終沒有被引用到的物件。

(2)標記清除法

標記清除法是現代垃圾回收演算法的思想基礎

標記清除法將垃圾回收分為兩個階段:標記階段和清除階段。

在標記階段,首先通過根節點,標記所有從根節點開始的可達物件,因此未被標記的物件就是未被引用的垃圾物件然後在清除階段,清除所有未被標記的物件。這種方法可以解決迴圈引用的問題,只有兩個物件不可達,即使它們互相引用也無濟於事。也是會被判定位不可達物件。

標記清除演算法可能產生的最大的問題就是空間碎片

如下圖所示,簡單描述了使用標記清除法對一塊連續的記憶體空間進行回收。

從根節點開始(在這裡僅顯示了兩個根節點),所有的有引用關係的物件均被標記為存活物件(箭頭表示引用)。從根節點起,不可達物件均為垃圾物件。在標記操作完成後,系統回收所有不可達物件。

 

從上圖可以看出,回收後的記憶體空間不再連續在物件的對空間分配過程中,尤其是大物件的記憶體分配,不連續記憶體空間的工作效率要低於連續空間的,這也是該演算法的缺點。

注意:標記清除演算法先通過根節點標記所有可達物件,然後清除所有不可達物件,完成垃圾回收後面會講到標記壓縮演算法,注意兩者的區別。。。。。。

(3) 複製演算法

演算法思想將原有的記憶體空間分為兩塊相同的儲存空間,每次只使用一塊,在垃圾回收時,將正在使用的記憶體塊中存活物件複製到未使用的那一塊記憶體空間中,之後清除正在使用的記憶體塊中的所有物件,完成垃圾回收。

如果系統中的垃圾物件很多,複製演算法需要複製的存活物件就會相對較少(適用場景)。因此,在真正需要垃圾回收的時刻,複製演算法的效率是很高的。而且,由於存活物件在垃圾回收過程中是一起被賦值到另一塊記憶體空間中的,因此,可確保回收的記憶體空間是沒有碎片的。(優點)

但是複製演算法的代價是將系統記憶體空間折半,只使用一半空間,而且如果記憶體空間中垃圾物件少的話,複製物件也是很耗時的,因此,單純的複製演算法也是不可取的。(缺點)

圖解演算法回收流程:

A、B兩塊相同的記憶體空間(原有記憶體空間折半得到的兩塊相同大小記憶體空間AB),A在進行垃圾回收,將存活的物件複製到B中,B中的空間在複製後保持連續。完成複製後,清空A。並將空間B設定為當前使用記憶體空間。

 

在java中的新生代序列垃圾回收器中,使用了複製演算法的思想,新生代分為eden空間、from空間和to空間3個部分,其中from和to空間可以看做用於複製的兩塊大小相同、可互換角色的記憶體空間塊(同一時間只能有一個被當做當前記憶體空間使用,另一個在垃圾回收時才發揮作用),from和to空間也稱為survivor空間,用於存放未被回收的物件。


新生代物件】:存放年輕物件的堆空間,年輕物件指剛剛建立,或者經歷垃圾回收次數不多的物件。

老年代物件】:存放老年物件的堆空間。即為經歷多次垃圾回收依然存活的物件。

在垃圾回收時,eden空間中存活的物件會被複制到未使用的survivor空間中(圖中的to),正在使用的survivor空間(圖中的from)中的年輕物件也會被複制到to空間中(大物件或者老年物件會直接進入老年代,如果to空間已滿,則物件也會進入老年代)。此時eden和from空間中剩餘物件就是垃圾物件,直接清空,to空間則存放此次回收後存活下來的物件。

優點:這種複製演算法保證了記憶體空間的連續性,又避免了大量的空間浪費。

注意:複製演算法比較適用於新生代。因為在新生代中,垃圾物件通常會多於存活物件,演算法的效果會比較好。

(4) 標記壓縮演算法

複製演算法的高效性是建立在存活物件少、垃圾物件多的情況下,這種情況在新生代比較常見,

但是在老年代中,大部分物件都是存活的物件,如果還是有複製演算法的話,成本會比較高。因此,基於老年代這種特性,應該使用其他的回收演算法。

標記壓縮演算法是老年代的回收演算法,它在標記清除演算法的基礎上做了優化。(回憶一下,標記清除演算法的缺點,垃圾回收後記憶體空間不再連續,影響了記憶體空間的使用效率。。。)

和標記清除演算法一樣,標記壓縮演算法也首先從根節點開始,對所有可達的物件做一次標記,

但之後,它並不是簡單的清理未標記的物件,而是將所有的存活物件壓縮到記憶體空間的一端,之後,清理邊界外所有的空間

這樣做避免的碎片的產生,又不需要兩塊相同的記憶體空間,因此價效比高。

圖解其演算法工作過程:

通過根節點標記出所有的可達物件後,沿著虛線進行物件的移動,將所有的可達物件移到一端,並保持他們之間的引用關係,最後,清理邊界外的空間。

 

標記壓縮演算法的最終效果等同於標記清除演算法執行完成後,再進行一次記憶體碎片的整理,因此也稱之為標記清除壓縮演算法。

(5) 分代演算法

前面介紹的垃圾回收演算法中,並沒有一種演算法可以完全替代其他演算法,各自具有自己的特點和優勢,因此需要根據垃圾物件的特性選擇合適的垃圾回收演算法。

分代演算法思想:將記憶體空間根據物件的特點不同進行劃分,選擇合適的垃圾回收演算法,以提高垃圾回收的效率

 

通常,java虛擬機器會將所有的新建物件都放入稱為新生代的記憶體空間。

新生代的特點是:物件朝生夕滅,大約90%的物件會很快回收,因此,新生代比較適合使用複製演算法。

當一個物件經過幾次垃圾回收後依然存活,物件就會放入老年代的記憶體空間,在老年代中,幾乎所有的物件都是經過幾次垃圾回收後依然得以存活的,因此,認為這些物件在一段時間內,甚至在程式的整個生命週期將是常駐記憶體的。

老年代的存活率是很高的,如果依然使用複製演算法回收老年代,將需要複製大量的物件。這種做法是不可取的,根據分代的思想,對老年代的回收使用標記清除或者標記壓縮演算法可以提高垃圾回收效率。

注意:分代的思想被現有的虛擬機器廣泛使用,幾乎所有的垃圾回收器都區分新生代和老年代

對於新生代和老年代來說,通常新生代回收的頻率很高,但是每次回收的時間都很短,而老年代回收的頻率比較低,但是被消耗很多的時間。為了支援高頻率的新生代回收,虛擬機器可能使用一種叫做卡表的資料結構,卡表為一個位元位集合,每一個位元位可以用來表示老年代的某一區域中的所有物件是否持有新生代物件的引用,

這樣以來,新生代GC時,可以不用花大量時間掃描所有老年代物件,來確定每一個物件的引用關係,而可以先掃描卡表,只有當卡表的標記為1時,才需要掃描給定區域的老年代物件,而卡表為0的所在區域的老年代物件,一定不含有新生代物件的引用。

如下圖表示:

卡表中每一位表示老年代4KB的空間,卡表記錄為0的老年代區域沒有任何物件指向新生代,只有卡表為1的區域才有物件包含新生代物件的引用,因此在新生代GC時,只需要掃面卡表為1所在的老年代空間,使用這種方式,可以大大加快新生代的回收速度。

 

(6) 分割槽演算法

演算法思想:分割槽演算法將整個堆空間劃分為連續的不同小區間,

如圖所示:

每一個小區間都獨立使用,獨立回收。

演算法優點是:可以控制一次回收多少個小區間

通常,相同的條件下,堆空間越大,一次GC所需的時間就越長,從而產生的停頓時間就越長。為了更好的控制GC產生的停頓時間,將一塊大的記憶體區域分割成多個小塊,根據目標的停頓時間,每次合理的回收若干個小區間,而不是整個堆空間,從而減少一個GC的停頓時間。

 


相關推薦

圖解JVM垃圾回收演算法

1 簡單介紹下----->垃圾回收概念 GC中的垃圾,指的是存在於記憶體中的、不會再被使用的物件。而垃圾回收就是把那些不再被使用的物件進行清除,收回佔用的記憶體空間。如果不及時對記憶體中的垃圾

菜鳥學習JVM——垃圾回收演算法

Java垃圾回收演算法 所有的垃圾回收演算法都是為了解決三個問題: 哪些記憶體需要回收 什麼時候回收 怎麼回收 引用計數法(Reference Counting) 引用計數法原理很簡單,給每個物件分配一個計數器,當被引用時就加一,引用失效就減一。

JVM垃圾回收演算法與引數配置

★引用計數法  這是個古老而經典的垃圾收集演算法,其核心就是在物件被其他所引用時計數器+1,而當引用失效時-1,但是這種方式有非常嚴重的問題:無法處理迴圈引用的情況,還有就是每次進行加減操作比較浪費系統性能。 ★標記清除法  分為標記和清除兩個階段進行處理記憶體中的物件,當然

JVM垃圾回收演算法及收集器

  垃圾回收演算法 標記清除 標記-清除演算法將垃圾回收分為兩個階段:標記階段和清除階段。在標記階段首先通過根節點,標記所有從根節點開始的物件,未被標記的物件就是未被引用的垃圾物件。然後,在清除階段,清除所有未被標記的物件。標記清除演算法帶來的一個問題是會存在大量的空間碎片,因

jvm垃圾回收演算法以及回收器詳解

本文主要講述JVM中幾種常見的垃圾回收演算法和相關的垃圾回收器,以及常見的和GC相關的效能調優引數。 GC Roots 我們先來了解一下在Java中是如何判斷一個物件的生死的,有些語言比如Python是採用引用計數來統計的,但是這種做法可能會遇見迴圈引用的問題,在Ja

JVM垃圾回收演算法和幾種JVM垃圾收集器

一、JVM垃圾回收演算法 注意:只是簡單總結,不詳細解釋演算法概念,不理解自行百度。 1、複製演算法 2、標記-清理演算法 3、標記-整理演算法 4、兩個概念: 新生代:初始物件,一般是採用複製演算法,需要重點掌握理解,記憶體被分為一個Eden,兩個Survivor區。

jvm垃圾回收演算法

2018年11月01日 19:03:23 碼上碼下 閱讀數:4 標籤: jvm 垃圾回收

jvm垃圾回收演算法詳解

在JDK1.8+的版本中,JVM記憶體管理結構有了一定的優化調整。主要是方法區(持久代)取消變成了直接使用元資料區(直接記憶體)的方式,但是整體上JVM的結構並沒有大改,特別是我們最為關心的堆記憶體管理方式並沒有在JDK1.8+的版本中有什麼變化,所以圖中的結構

JVM 垃圾回收演算法回收器詳解

本文主要講述JVM中幾種常見的垃圾回收演算法和相關的垃圾回收器,以及常見的和GC相關的效能調優引數。 GC Roots 我們先來了解一下在Java中是如何判斷一個物件的生死的,有些語言比如Python是採用引用計數來統計的,但是這種做法可能會遇見迴圈引用的問題,在Java以及C#

JVM垃圾回收演算法解析

JVM垃圾回收演算法解析 標記-清除演算法 該演算法為最基礎的演算法。它分為標記和清除兩個階段,首先標記出需要回收的物件,在標記結束後,統一回收。該演算法存在兩個問題:一是效率問題,標記和清除過程效率都不太高,二是空間問題,在執行一次清除操作後,會存在好多不連續的記憶體碎片,從而造成資源的浪費。空間碎片

第四章 JVM垃圾回收演算法

注意:本文主要參考自《分散式Java應用:基礎與實踐》,與《深入理解Java虛擬機器(第二版)》中的一些說法有一些不同,但是原理一致 1、三種垃圾回收演算法 標記-清除(年老代) 標記-整理(即標記-壓縮)(年老代) 複製(年輕代) 1.1、標記-清除演算法 原理: 從根

JVM垃圾回收演算法垃圾收集器種類、常用垃圾收集器引數

現在的商業虛擬機器都採用這種方法進行回收新生代,但並不是將新生代分為兩個一樣大小的記憶體,由於考慮到98%的物件都是很快死亡的,所以將新生代分為 一個大的eden區和兩個小的survivor區,每次只使用eden區和其中一個survivor區,回收時將這兩個區中的存活物件複製到另外一個s區。預設情況eden區

JVM垃圾判定演算法+四種引用+JVM垃圾回收演算法

JVM垃圾判定演算法 常見的JVM垃圾判定演算法包括:引用計數演算法、可達性分析演算法。 引用計數演算法(Reference Counting) 1引用計數演算法是通過判斷物件的引用數量來決定物件是否可以被回收。 2給物件中新增一個引用計數器,每當有一個地方引用它

JVM垃圾回收演算法的優缺點

最近在學習JVM的一些知識,所以特意寫下學習筆記來簡單記錄知識點,由於只是初步的學習,下面本人所總結的內容都比較簡單且不一定正確,如果有什麼錯誤希望大家能指出來,我看到後會進行修正。 垃圾分析演算法 功能:分析JVM堆上哪些物件是“垃圾” 引用計數法

java架構之路-(12)JVM垃圾回收演算法垃圾回收

  接上次JVM虛擬機器堆記憶體模型來繼續說,上次我們主要說了什麼時候可能把物件直接放在老年代,還有我們的可能性分析,提出GCroot根的概念。這次我們主要來說說垃圾回收所使用的的演算法和我們的垃圾回收器,需要了解我們的可達性分析GCroot根是什麼,還有我們的動態年齡判斷和老年代分配擔保機制,還不清楚咋回事

3-JVM垃圾回收演算法垃圾收集器

# 垃圾回收演算法和垃圾收集器 ## 1.什麼是垃圾回收 對於記憶體當中無用的物件進行回收,如何去判斷一個物件是不是無用的物件。 ### 引用計數法: *每個物件中都會儲存一個引用計數,每增加一個引用就+1,消失一個引用就-1。當引用計數器為0時就會判斷該物件是垃圾,進行回收。* **但是這樣會有一

JVM知識(五):垃圾回收演算法

        在介紹垃圾回收演算法之前,我們需要先了解一個詞“stop the world”,stop the world會在執行某一個垃圾回收演算法的時候產生,JVM為了執行垃圾回收,會暫時java應用程式的執行,等垃圾回收完成後,再繼續執行。如果你使用JMeter測試

JVM垃圾回收機制演算法分析

JVM記憶體執行時資料區 一、什麼是垃圾回收機制gc垃圾回收機制&&演算法 什麼是垃圾回收機制: 不定時去堆記憶體清理不可達物件。不可達的物件並不會馬上就會直接回收,而是至少要經過兩次標記的過程。 public class Test { public st

Jvm垃圾回收器(終結篇) Jvm垃圾回收器(基礎篇) Jvm垃圾回收器(演算法篇)

Jvm垃圾回收目前就準備了這三篇博文進行整理,在寫博文的過程中我也是邊看邊記載的,我覺得這種學習方式更容易讓人記住,不會輕易忘記。以前的學習模式都是看PDF文件、看書等,但是有個缺點就是當時記住了過段時間就會忘記,因此想把學習過程中重要的部分做個筆記總結,以便於後期複習回顧(學習技巧僅個人觀點)同時也希望lz

jvm 三種垃圾回收演算法:標記-清除、複製演算法、標記-整理

標記-清除:先標記出GC Roots能關聯到的物件,然後清除這些被標記的物件,剩下的就是存活的物件了。 缺點: 1、清除需要被清理的物件後剩下的記憶體都是破碎的,如果要建立大物件,可能會因為找不到足夠的記憶體而再次觸發垃圾收集。 2、標記和清除的效率相對於其他演算法來說都不高,標記的原理