首頁 > 軟體

JVM的垃圾回收演演算法一起來看看

2022-03-08 16:00:20

垃圾回收演演算法

概念

垃圾回收(Garbage Collection,GC)。程式的執行需要資源,無效的物件如果不及時清理就會一直佔用資源,所以對記憶體資源管理就變得十分重要。而Java為了讓我們更多的關注程式碼本身,而不用過多的考慮記憶體的釋放問題,就有了我們十分熟悉的GC。然而當垃圾回收成為系統達到更高並行量的瓶頸時,我們就需要對這些自動化的技術進行一系列的監控和調節。 

GC主要需要完成三件事情 :

哪些記憶體需要回收?
什麼時候回收?
如何回收?

哪些垃圾需要回收呢?這個時候我們如何判斷哪些物件“活著”,哪些物件“死去”?於是就有了標記演演算法。

1.標記演演算法

垃圾收集器中標記演演算法有兩種,參照計數法和根可達演演算法

1.1 參照計數法(Reference Counting)

參照計數演演算法很簡單,它實際上是通過在物件頭中分配一個空間來儲存該物件被參照的次數。如果該物件被其它物件參照,則它的參照計數加1,如果刪除對該物件的參照,那麼它的參照計數就減1,當該物件的參照計數為0時,那麼該物件就會被回收。

如:

A objA = new A();
B objB = new B();
objA.ref = objB;

如圖:

物件 A 的範例在Java堆中就是一塊記憶體而已,而objA 做為一個區域性變數參照了它,所以它的參照計數就是1,物件B的範例在堆中也是一塊記憶體,objB這個區域性變數參照了它,然後objA又參照了它一次,所以它的參照計數就是2。

客觀來說,參照計數演演算法 效率高,實現簡單,然而,Java虛擬機器器沒有選取參照計數演演算法來管理記憶體,主要是因為無法解決 迴圈參照的問題

如:

objA.ref= objB;
objB.ref= objA

如圖:

實際上這兩個物件已經不可能再被存取,但是它們因為互相參照著對方,導致它們的參照計數都不為0,於是這兩個物件都無法被GC回收。

1.2 可達性分析演演算法(Reachable Analysis)

在Java中是通過可達性分析演演算法來判斷物件是否存活的。選定一系列稱為"GC ROOTS"的物件作為起始點,從這些物件向下搜尋,搜尋所走過的道路稱為參照鏈(Reference Chain).當一個物件到GC ROOTS沒有任何參照鏈時,則不可達,這些物件會被判定可以回收。

如圖:

在Java中,能作為GC Roots的物件包含以下幾種

虛擬機器器棧(棧幀中的本地變數表)中參照的物件
方法區中類靜態屬性參照的物件
方法區中常數參照的物件
本地方法棧JNI(即一般說的Native方法)當中參照的物件

2.回收演演算法

當成功區分出哪些是存活物件哪些是死亡物件之後,GC接下來的任務就是執行垃圾回收,釋放掉無用物件所佔用的記憶體空間,以便有足夠的可用記憶體空間為新物件分配記憶體。常用的垃圾回收演演算法有 標記清除演演算法、複製演演算法、標記壓縮演演算法。

2.1 標記清除演演算法 (Mark Sweep)

標記清除演演算法是最基礎的垃圾回收演演算法,同它的名字一樣,該演演算法有兩個過程,首先標記哪些是可回收的物件,然後進行記憶體回收

標記: Collector從參照根結點開始遍歷,標記所有被參照的物件。一般是在物件的Header中記錄為可達物件。

清除: Collector對堆記憶體從頭到尾進行線性的遍歷,如果發現某個物件在其Header中沒有標記為可達物件,則將其回收。從網上找張圖給大家解釋一下,

如圖:

缺點:

1.效率不高,標記過程和清除過程效率都一般

2.會產生很多空間碎片,可能會導致以後為大物件分配空間時因為找不到可用的連續記憶體空間不得不再次進行GC。

2.2 複製演演算法(Copying)

GC複製演演算法(Copying GC)是由Marvin L. Minsky在1963年研究出來的演演算法。原理是把記憶體分為兩個空間一個是From空間,一個是To空間,物件一開始只在From空間分配,To空間是空閒的。GC時把存活的物件從From空間複製貼上到To空間,之後把To空間變成新的From空間,原來的From空間變成To空間。回收前後對比下圖所示:

如圖:

優缺點:

1.複製演演算法實現簡單執行高效,不會產生記憶體碎片

2.但是將記憶體縮小為原本的一半,代價略高。

現在虛擬機器器基本都採用這種垃圾回收演演算法回收新生代

2.3 標記壓縮演演算法(Mark-Compact)

標記壓縮演演算法(Mark-Compact),標記過程和標記清除演演算法的標記過程一樣,但是清理過程不同,會將存活物件移動到一端,然後清理掉端邊界之外的記憶體,

如圖:

優缺點:

標記整理演演算法效率低,但不用浪費記憶體,也不會造成記憶體碎片。

2.4 分代回收演演算法

因為新生代物件大量死去,少量存活,一般採用複製演演算法。老年代存活率高,回收的少,一般採用MC/MS(標記清除/標記壓縮)

如圖是我用arthas的dashboard命令輸出的原生的Memory資訊。jdk1.8預設的垃圾回收器是ps+po(這個之後講)。可以看到新生代大小(伊甸區和s區),老年代大小。

2.4.1 新生代(Eden區/伊甸區)

年輕代的物件處於一種“朝生夕死”的狀態,在年輕代的GC叫做YGC(Minor GC)。Eden區物件活過第一次垃圾回收之後會進入survivor區(S0S1/S1S2)。在S1,S2之間經過多次垃圾回收進入老年代。

-XX:MaxTenuringThreshold 可以設定多少次從年輕代進入老年代

在多執行緒那我們整過這張圖,再看一下,分代年齡只有4bit,意味著物件的最大年齡只有15-----可以通過上面的引數設定大小,最大15,之後要是沒有被gc就會進入老年代。

2.4.2 老年代(tenured/old)

進入老年代的物件大多數活過了年輕代的多次gc,因此不會頻繁死亡,老年代的GC叫做(Major GC)FULL GC。FGC的效率比YGC低的多,在老年代無法繼續分配空間的時候觸發,觸發是新生代老年代一起進行回收。

2.4.3 新生代何時進入老年代

1. 超過 XX:MaxTenuringThreshold 指定次數
2. 動態年齡,S0->S1超過50%,把年齡最大的放到Old
3. 分配擔保:YGC期間,survivor區空間不夠了,空間擔保直接進入老年代

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!  


IT145.com E-mail:sddin#qq.com