1、垃圾收集算法
1.1、標記清除法
標記清除就像他的名字一樣,他分為兩個階段:
- 第一個階段就是標記出所有需要回收的對象
- 第二個階段就是進行垃圾回收,也就是清除掉我們第一階段標記的對象
我們可以看一下下面的這個圖
這就是一個標記清除的過程,很簡單。
這里面有一個需要關注的點:我們的第一個階段標記,他是怎么進行的標記呢?
對于這種標記的方式,其實也是有兩種方式的:
- 引用計數法
- 可達性分析算法
note:關于這兩種方式的具體,我們可以看一下我上一篇文章的總結《深入理解jvm》讀書筆記之——判斷對象存活的方法
那么我說一下,對于標記清除而言,他沒有使用引用計數法,而是使用了gc root的標記方式.大家思考一下為什么?
是這樣的,引用計數雖然是一個很高效簡單的方式,但是對于循環(huán)引用的對象,他是很難去判斷的.
假如內存中有2個對象,a和b,其實這時候只有a中有b,b中有a,但是整個jvm中沒人要使用他們了,但是這種情況下,a和b的計數器還是1,所以就不會被標記,也就不會被gc掉.
1.2、復制算法
我們先思考一下,標記清楚是完美的嗎?
很顯然不是,他的弊端在哪里?
Q:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除之后會產生大量不連續(xù)的內存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內存而不得不提前觸發(fā)另一次垃圾收集動作。
所以我們這時候要講一下復制算法了
它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區(qū)進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
我們看一下這個圖,這就是我們整個復制算法的過程,然后針對Hotspot的年輕代而言,也是基于這種算法來做的,這種算法的好處我上面也說了,這樣使得每次都是對整個半區(qū)進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
復制算法的弊端
但是我們繼續(xù)思考一下,這種算法是否有弊端,相信大家一眼就看出來。
制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況
所以.我們的hotspot虛擬機中的老年代并沒有使用復制的算法,那么他使用的是啥呢?標記-整理算法
1.3、標記-整理算法
標記整理這種算法和標記清除有點類似,第一個階段也是先標記,標記也是用的我們上面的gc root鏈的可達性分析算法.然后我們和標記清除不同的是第二部,這種算法讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
大家可以看這個圖,就是標記整理的算法的過程,那么對于這個算法,和標記清除的缺點是一樣的,他雖然解決了空間的問題,但是對于標記這個階段還是不夠高效.那么究竟使用哪種算法最合適呢?那么就引入了我們的第四中算法:
分代.
1.4、分代算法
其實分代很簡單,就是hotspot虛擬機講整個jvm整個內存區(qū)域分為老年代,年輕帶,永久代。根據不同的地方,不同的特性采用不同的算法來進行gc,這就是我們分代算法的實現。
2、hotspot中算法的實現
2.1、安全點
hotspot中利用OopMap來記錄對象內什么偏移量是什么類型的數據之類的,jit編譯的時候也會記錄。gc掃描的時候去掃描OopMap就可以了。
這個oopMap主要是記錄的特點位置,也就是我們這里要提到的安全點
。
什么是安全點
呢?
大多數垃圾收集器進行gc的時候,需要進行stop the world ,停止jvm所有的線程,只有所有線程都到達這個
點
的時候,才可以進行gc,這個點就是安全點
對于gc收集器來講,如何在gc時候快速到達安全點
是一個關鍵點,有以下兩種方式(現在基本都用的是主動式中斷):
- 搶先式中斷:無需線程配合,gc時,吧所有線程全部中斷,如果有線程中斷的地方不在安全點就恢復該線程,讓他跑到安全點上。
- 主動式中斷:gc需要中斷線程的時候,不直接對線程操作,簡單的設置一個標志,各線程輪詢這個標志,發(fā)現中斷標志變了的時候,就自己中斷掛起,輪詢標志的地方和安全點是重合的,另外再加上創(chuàng)建對象需要分配內存的地方。
2.2、安全域
在線程sleep或者blocked的時候是沒辦法主動輪詢中斷標志的,也就無法響應gc的中斷請求,這種情況下,就誕生了安全域
。
安全域
:一段代碼里,引用關系不會變,在這端代碼的任何地方gc都是安全的。
線程執(zhí)行到安全域
的時候就標示自己,然后jvm要gc的時候就不用管這些進入安全域
的線程了,線程離開安全域
的時候,會檢測系統(tǒng)是否完成了GC。