下面即將與各位分享的,是GC演算法中最基礎的演算法------標記/清除演算法。如果搞清楚這個演算法,那麼後面兩個就完全是小菜一碟了。
首先,我們回想一下上一章提到的根搜尋演算法,它可以解決我們應該回收哪些物件的問題,但是它顯然還不能承擔垃圾蒐集的重任,因為我們在程式(程式也就是指我們運行在JVM上的JAVA程式)運作期間如果想進行垃圾回收,就必須讓GC執行緒與程式當中的執行緒互相配合,才能在不影響程式運作的前提下,順利的將垃圾進行回收。
為了達到這個目的,標記/清除演算法就應運而生了。它的做法是當堆中的有效記憶體空間(available memory)被耗盡的時候,就會停止整個程式(也被成為stop the world),然後進行兩項工作,第一項則是標記,第二項則是清除。
下面LZ具體解釋一下標記和清除分別都會做些什麼。
標記:標記的過程其實就是,遍歷所有的GC Roots,然後將所有GC Roots可達的物件標記為存活的物件。
清除:清除的過程將遍歷堆中所有的對象,將沒有標記的對象全部清除掉。
其實這兩個步驟並不是特別複雜,也很容易理解。 LZ用通俗的話解釋一下標記/清除演算法,就是當程式運行期間,若可以使用的記憶體耗盡的時候,GC執行緒就會被觸發並將程式暫停,隨後將依舊存活的物件標記一遍,最終再將堆中所有沒被標記的物件全部清除掉,接下來便讓程式恢復運作。
下面LZ給各位製作了一組描述上面過程的圖片,結合著圖片,我們來直觀的看下這一過程,首先是第一張圖。
這張圖代表的是程式運作期間所有物件的狀態,它們的標誌位元全部是0(也就是未標記,以下預設0就是未標記,1為已標記),假設這會兒有效記憶體空間耗盡了,JVM將會停止應用程式的運行並開啟GC線程,然後開始進行標記工作,按照根搜尋演算法,標記完以後,物件的狀態如下圖。
可以看到,按照根搜尋演算法,所有從root對象可達的對象就被標記為了存活的對象,此時已經完成了第一階段標記。接下來,就要執行第二階段清除了,那麼清除完以後,剩下的物件以及物件的狀態如下圖所示。
可以看到,沒有被標記的物件將會回收清除掉,而被標記的物件將會留下,並且會將標記位元重新歸0。接下來就不用說了,喚醒停止的程式線程,讓程式繼續運行即可。
其實這過程並不複雜,甚至可以說非常簡單,各位說對嗎。不過其中有一點值得LZ一提,就是為什麼非要停止程式的運作呢?
這個其實也不難理解,LZ舉個最簡單的例子,假設我們的程式與GC執行緒是一起運作的,各位試想這樣一種場景。
假設我們剛標記完圖中最右邊的那個對象,暫且記為A,結果此時在程式當中又new了一個新對象B,且A對象可以到達B對象。但由於此時A物件已經標記結束,B物件此時的標記位元依然是0,因為它錯過了標記階段。因此當接下來輪到清除階段的時候,新物件B將會被苦逼的清除掉。如此一來,不難想像結果,GC執行緒將會導致程式無法正常運作。
上面的結果當然令人無法接受,我們剛new了一個對象,結果經過一次GC,忽然變成null了,這怎麼玩?
到此為止,標記/清除演算法LZ已經介紹完了,下面我們來看下它的缺點,其實了解完它的演算法原理,它的缺點就很好理解了。
1、首先,它的缺點就是效率比較低(遞歸與全堆物件遍歷),而且在進行GC的時候,需要停止應用程序,這會導致用戶體驗非常差勁,尤其對於互動式的應用程式來說簡直是無法接受。試想一下,如果你玩一個網站,這個網站一個小時就掛五分鐘,你還玩嗎?
2、第二點主要的缺點,則是這種方式清理出來的空閒內存是不連續的,這點不難理解,我們的死亡對像都是隨即的出現在內存的各個角落的,現在把它們清除之後,記憶體的佈局自然會亂七八糟。而為了應付這一點,JVM就必須維持一個記憶體的空閒列表,這又是一種開銷。而且在分配數組物件的時候,尋找連續的記憶體空間會不太好找。
看完它的缺點估計有的猿友要忍不住吐糟了,「這麼說這個演算法根本沒法用嘛,那LZ還介紹這麼個玩意幹什麼。」
猿友們莫要著急,一個算法有缺點,高人們自然會想盡辦法去完善它的。而接下來我們要介紹的兩種演算法,都是在標記/清除演算法的基礎上優化而產生的。具體的內容,下次LZ再和各位分享。
本次的分享就到此結束了,希望各位看完都能有所收穫,0.0。
以上就是JVM記憶體管理------GC演算法精解(五分鐘讓你徹底明白標記/清除演算法)的內容,更多相關內容請關注PHP中文網(www.php.cn)!