图解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的停頓時間。
?
from:?http://blog.csdn.net/wen7280/article/details/54428387 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的图解JVM垃圾回收算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM垃圾回收算法 总结及汇总
- 下一篇: JVM调优总结(4):分代垃圾回收