之前看過了垃圾回收算法的新生代GC,也是使用的一種比較浪費內存的復制算法,晚上看書又接着往下看了一點,
堆 = 新生代+老年代,但是要注意一點老年代不包括永久代(方法區),也就是說堆內存中只有新生代和老年代,而永久代是指的方法區。
之前介紹過新生代中的垃圾回收機制了,再來介紹一下老年代的垃圾回收機制里面使用到的算法。
新生代GC:MinorGC 之前介紹過了不說了,復制算法圖解也比較清晰
老年代GC:FullGC 我們先說FullGC出現的原因吧,FullGC是老年代的GC,在新生代如果說存在的對象或者說新創建 出來的對象由於某些原因需要移動到老年代中,但是老年代中壓根就沒有這么大的內存空間去容納這個對象, 那么就會引發一次FullGC,如果在執行完FullGC之后,還是沒有辦法給這些對象分配內存,那么涼了,該拋出異常了,異常類型就是OutOfMemoryError。
而FullGC使用的是和MinorGC不一樣的算法,它使用的是標記清除算法,聽名字,挺好理解的,來波圖示解析一波。 深入了解JVM一書中的圖示是這個樣子的,
看名字的話是先標記,然后在刪除。這也是也給最最基本的算法。 這個算法就是分兩個步驟
•標記(Mark)過程:找到所有的可以訪問的對象,做個指定的標記。
•清除(Swep)過程:遍歷堆內存,把未標記的對象進行一個回收。
之前看一些文檔上說,先標記,然后把沒有標記的對象給回收掉,其實意思都差不多,但是在深入理解JVM一書中說到,首先標記出所有的需要回收的對象,在標記完成之后統一回收所有的被標記的對象, 其實我的理解和書中感覺有點不太一樣,不過區別也不大。我說說我的理解吧。
在了解了這個之后,我們還得說一個概念,那就是GC Root,Root我們可以理解成一個根節點就像這個樣子
上圖中的a,b,c,d,就是活着的對象,如果說存在這引用,比如說b引用的a,那么a他就是屬於活着的對象。 當我們老年代內存區中的有效的內存空間不夠的時候,那么這時候整個世界都要安靜下來了(stop the world),這時候就要開始准備進行垃圾回收了。
•標記:遍歷所有的GC Roots,然后將所有GC Roots可達的對象標記為存活的對象。就是我們圖中所標記的a,b,c,d.•清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。 也就是說,如果內存不夠,GC線程就會被觸發然后將程序暫停,隨后將依舊存活的對象標記一遍,最后再將堆中所有沒被標記的對象全部清除掉,接下來便讓程序繼續恢復運行。
流程圖就想這個樣子的 初始下的老年代中的對象狀態
這時候都是沒有被標記的狀態,接下來內存不夠,GC線程停止,開始進行標記了
按照根節點開始遍歷 標記的abcdeh都是存活的對象,接下來開始標記。
接下來就是清除數據了,這個就更加的簡單了
清楚完成之后還有就是把標記去除掉,可以下次進行標記清除的時候繼續清除
這樣標記清除就執行完畢了,剩下還有兩個要說的地方,
一是在進行標記清楚算法的時候為什么要讓程序停止,(stop the world)。
二是標記清除算法的優點和缺點又是什么?(Stop the World)
程序停止其實可以理解,因為如果說不停止程序的話,我們在標記完成這個b對象之后,我們new出一個新的對象J,可以從B指向J,也就是說,這時候J應該是被標記的狀態,但是實際情況肯定不是,這個對象在B標記完之后,馬上都要結束了,我們又new出來一個對象,可想而知,他肯定是沒有被標記的,所以在第二階段進行清除的時候,這個苦命的J將會被清除掉, 那這樣肯定是不符合我們的實際情況的。
你想呀這剛剛new出來的對象結果被清除了,忽然變成了空值,那就不符合我們的要求了。所以他會讓程序先停止,然后不會再出現這種情況,然后進行開始標記階段。
優缺點
首先我們可以先看缺點,他的缺點非常明顯,
•因為他會遞歸遍歷Root,這樣的話 Stop the World的時間就比較長了,這樣一直讓人等待的滋味可不是那么好受。•第二個就是這種清除方式清除出來的內存空間是不連續的,你看這個圖
死亡的上下分成了2部分,是不連續的,這樣給JVM又造成了一種額外的負擔,他需要去維持一個內存的空閑列表,如果說我們在這時候去new一個數組,你想想他去找這個連續的內存空間的話,是不是就要困難很多呢?
他的優點也有,
•比如說不會出現循環引用, 我們可以想想 兩個類 互相引用,A中newB,B中newA,那這樣豈不是a.b=b ,b.a = a ,是吧 ,而標記清除算法在走完了之后,是可以回收a,和b的,因為他是從根元素開始遍歷標記,也就是從ab開始,那么單一的a和單一的b就是沒有被標記的,所以,這樣就避免了循環引用的問題•還有一點感覺沒啥區別,都是內存不夠的時候才進行的引用。這沒啥說的。
標記--整理算法
而因為標記--清除算法會導致內存分配都出現了各種不均勻的空間,這時候就有了另外的一種算法,直接把那些存活的對象標記出來,然后給他懟到內存空間邊界,然后剩下的直接全給他清除了。這方法圖解看的一清二楚,剩下的都是和標記清除算法一樣的,好像沒啥解釋的,直接上圖
書中你看就是把存活的都給懟到內存空間的上邊,你也可以隨便的理解成上下左右都ok。
以上就是堆內存中的老年代的兩種垃圾回收算法了,如果有不合適的,希望大佬可以指正,一起討論一下。
Java 極客技術公眾號,是由一群熱愛 Java 開發的技術人組建成立,專注分享原創、高質量的 Java 文章。如果您覺得我們的文章還不錯,請幫忙贊賞、在看、轉發支持,鼓勵我們分享出更好的文章。
關注公眾號,大家可以在公眾號后台回復“博客園”,免費獲得作者 Java 知識體系/面試必看資料。