JAVA虛擬機學習之GC算法深度解析


垃圾收集 Garbage Collection 通常被稱為“GC”,它誕生於1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。

jvm 中,程序計數器、虛擬機棧、本地方法棧都是隨線程而生隨線程而滅,棧幀隨著方法的進入和退出做入棧和出棧操作,實現了自動的內存清理,因此,我們的內存垃圾回收主要集中於 java 堆和方法區中,在程序運行期間,這部分內存的分配和使用都是動態的.


JAVA虛擬機學習之GC算法深度解析


JAVA虛擬機學習之GC算法深度解析

>>>> GC:Garbage Collection 垃圾收集

GC:Garbage Collection 垃圾收集。這裡所謂的垃圾指的是在系統運行過程當中所產生的一些無用的對象,這些對象佔據著一定的內存空間,如果長期不被釋放,可能導致OOM

在C/C++裡是由程序猿自己去申請、管理和釋放內存空間,因此沒有GC的概念。而在Java中,後臺專門有一個專門用於垃圾回收的線程來進行監控、掃描,自動將一些無用的內存進行釋放,這就是垃圾收集的一個基本思想,目的在於防止由程序猿引入的人為的內存洩露。

>>>>1960年 Lisp使用了GC

事實上,GC的歷史比Java久遠,1960年誕生於MIT的Lisp是第一門真正使用內存動態分配和垃圾收集技術的語言。當Lisp還在胚胎時期時,人們就在思考GC需要完成的3件事情:

哪些內存需要回收?

什麼時候回收?

如何回收?

>>>>Java中,GC的對象是Java堆和方法區(即永久區)

內存區域中的程序計數器、虛擬機棧、本地方法棧這3個區域隨著線程而生,線程而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧的操作,每個棧幀中分配多少內存基本是在類結構確定下來時就已知的。在這幾個區域不需要過多考慮回收的問題,因為方法結束或者線程結束時,內存自然就跟著回收了

Java堆和方法區則不同,一個接口中的多個實現類需要的內存可能不同,一個方法中的多個分支需要的內存也可能不一樣,我們只有在程序處於運行期間時才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,GC關注的也是這部分內存,後面的文章中如果涉及到“內存”分配與回收也僅指著一部分內存。

引用計數算法

老牌垃圾回收算法。無法處理循環引用,沒有被Java採納

引用計數算法的概念:

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。

JAVA虛擬機學習之GC算法深度解析

使用者舉例:

引用計數算法的實現簡單,判定效率也高,大部分情況下是一個不錯的算法。很多地方應用到它。例如:

微軟公司的COM技術:Computer Object Model

使用ActionScript3的FlashPlayer

Python

但是,主流的java虛擬機並沒有選用引用計數算法來管理內存,其中最主要的原因是:它很難解決對象之間相互循環引用的問題

引用計數算法的問題:

  • 引用和去引用伴隨加法和減法,影響性能
  • 致命的缺陷:對於循環引用的對象無法進行回收
JAVA虛擬機學習之GC算法深度解析

上面的3個圖中,對於最右邊的那張圖而言:循環引用的計數器都不為0,但是他們對於根對象都已經不可達了,但是無法釋放。

循環引用的代碼舉例:

public class Object {

Object field = null;

public static void main(String[] args) {

Thread thread = new Thread(new Runnable() {

public void run() {

Object objectA = new Object();

Object objectB = new Object();//位置1

objectA.field = objectB;

objectB.field = objectA;//位置2

//to do something

objectA = null;

objectB = null;//位置3

}

});

thread.start();

while (true);

}

}

上方代碼看起來有點刻意為之,但其實在實際編程過程當中,是經常出現的,比如兩個一對一關係的數據庫對象,各自保持著對方的引用。最後一個無限循環只是為了保持JVM不退出,沒什麼實際意義。

代碼解釋:

代碼中標註了1、2、3三個數字,當位置1的語句執行完以後,兩個對象的引用計數全部為1。當位置2的語句執行完以後,兩個對象的引用計數就全部變成了2。當位置3的語句執行完以後,也就是將二者全部歸為空值以後,二者的引用計數仍然為1。根據引用計數算法的回收規則,引用計數沒有歸0的時候是不會被回收的。

對於我們現在使用的GC來說,當thread線程運行結束後,會將objectA和objectB全部作為待回收的對象。而如果我們的GC採用上面所說的引用計數算法,則這兩個對象永遠不會被回收,即便我們在使用後顯示的將對象歸為空值也毫無作用。

根搜索算法

根搜索算法的概念:

由於引用計數算法的缺陷,所以JVM一般會採用一種新的算法,叫做根搜索算法。它的處理方式就是,設立若干種根對象,當任何一個根對象到某一個對象均不可達時,則認為這個對象是可以被回收的

JAVA虛擬機學習之GC算法深度解析

如上圖所示,ObjectD和ObjectE是互相關聯的,但是由於GC roots到這兩個對象不可達,所以最終D和E還是會被當做GC的對象,上圖若是採用引用計數法,則A-E五個對象都不會被回收。

可達性分析:

我們剛剛提到,設立若干種根對象,當任何一個根對象到某一個對象均不可達時,則認為這個對象是可以被回收的。我們在後面介紹標記-清理算法/標記整理算法時,也會一直強調從根節點開始,對所有可達對象做一次標記,那什麼叫做可達呢?這裡解釋如下:

從根(GC Roots)的對象作為起始點,開始向下搜索,搜索所走過的路徑稱為“引用鏈”,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的概念來講,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

根(GC Roots):

說到GC roots(GC根),在JAVA語言中,可以當做GC roots的對象有以下幾種:

1、棧(棧幀中的本地變量表)中引用的對象。

2、方法區中的靜態成員。

3、方法區中的常量引用的對象(全局變量)

4、本地方法棧中JNI(一般說的Native方法)引用的對象。

注:第一和第四種都是指的方法的本地變量表,第二種表達的意思比較清晰,第三種主要指的是聲明為final的常量值。

在根搜索算法的基礎上,現代虛擬機的實現當中,垃圾蒐集的算法主要有三種,分別是標記-清除算法、複製算法、標記-整理算法。這三種算法都擴充了根搜索算法,不過它們理解起來還是非常好理解的。

標記-清除算法

標記清除算法的概念:

標記-清除算法是現代垃圾回收算法的思想基礎。標記-清除算法將垃圾回收分為兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;然後,在清除階段,清除所有未被標記的對象。

JAVA虛擬機學習之GC算法深度解析

標記-清除算法詳解:

它的做法是當堆中的有效內存空間(available memory)被耗盡的時候,就會停止整個程序(也被成為stop the world),然後進行兩項工作,第一項則是標記,第二項則是清除。

  • 標記:標記的過程其實就是,遍歷所有的GC Roots,然後將所有GC Roots可達的對象標記為存活的對象。
  • 清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。

就是當程序運行期間,若可以使用的內存被耗盡的時候,GC線程就會被觸發並將程序暫停,隨後將依舊存活的對象標記一遍,最終再將堆中所有沒被標記的對象全部清除掉,接下來便讓程序恢復運行。

來看下面這張圖:

JAVA虛擬機學習之GC算法深度解析

上圖代表的是程序運行期間所有對象的狀態,它們的標誌位全部是0(也就是未標記,以下默認0就是未標記,1為已標記),假設這會兒有效內存空間耗盡了,JVM將會停止應用程序的運行並開啟GC線程,然後開始進行標記工作,按照根搜索算法,標記完以後,對象的狀態如下圖:

JAVA虛擬機學習之GC算法深度解析

上圖中可以看到,按照根搜索算法,所有從root對象可達的對象就被標記為了存活的對象,此時已經完成了第一階段標記。接下來,就要執行第二階段清除了,那麼清除完以後,剩下的對象以及對象的狀態如下圖所示:

JAVA虛擬機學習之GC算法深度解析

上圖可以看到,沒有被標記的對象將會回收清除掉,而被標記的對象將會留下,並且會將標記位重新歸0。接下來就不用說了,喚醒停止的程序線程,讓程序繼續運行即可。

疑問:為什麼非要停止程序的運行呢?

答:

這個其實也不難理解,假設我們的程序與GC線程是一起運行的,各位試想這樣一種場景。

假設我們剛標記完圖中最右邊的那個對象,暫且記為A,結果此時在程序當中又new了一個新對象B,且A對象可以到達B對象。但是由於此時A對象已經標記結束,B對象此時的標記位依然是0,因為它錯過了標記階段。因此當接下來輪到清除階段的時候,新對象B將會被苦逼的清除掉。如此一來,不難想象結果,GC線程將會導致程序無法正常工作。

上面的結果當然令人無法接受,我們剛new了一個對象,結果經過一次GC,忽然變成null了,這還怎麼玩?

標記-清除算法的缺點:

(1)首先,它的缺點就是效率比較低(遞歸與全堆對象遍歷),導致stop the world的時間比較長,尤其對於交互式的應用程序來說簡直是無法接受。試想一下,如果你玩一個網站,這個網站一個小時就掛五分鐘,你還玩嗎?

(2)第二點主要的缺點,則是這種方式清理出來的空閒內存是不連續的,這點不難理解,我們的死亡對象都是隨即的出現在內存的各個角落的,現在把它們清除之後,內存的佈局自然會亂七八糟。而為了應付這一點,JVM就不得不維持一個內存的空閒列表,這又是一種開銷。而且在分配數組對象的時候,尋找連續的內存空間會不太好找。

Jacky說

Gc算法在學習JVM的時候是必不可少的,同學們不要懈怠它哦。

同時,也希望學習Java還有其他語言的小夥伴們一直保持一顆熱情的心。

如果想了解更多Java的知識,可以點擊 瞭解更多

最後送大家一句:

Nothing great was ever achieved without enthusiasm.

——愛默生

Java GC算法 垃圾收集器

Java虛擬機詳解04----GC算法和種類

實訓邦官網

———— / END / ————

我會長成大樹,等你贊聲良木


分享到:


相關文章: