JVM的垃圾回收機制(簡稱GC)
? ? ? ? JVM的垃圾回收機制非常強大,是JVM的一個很重要的功能,而且這也是跟對象實例息息相關的,如果對象實例不用了要怎么清除呢?
如何判斷對象已經沒用了
當JVM認為一個對像已經沒用了,就會把這個對象判定為是垃圾,就會去回收它的空間,有兩個方法判斷一個對像是否已經沒用了
1、引用計數法:記錄指向該對象的引用數,當該數值為零時就將該對象判定為垃圾
這個方法實現簡單,判定效率也高,不過它有個致命的問題,它無法解決相互對象之間相互循環引用的問題,看下面這個例子
?
public class Test {public Object object = null;public static void main(String[] args) {Test a = new Test();//對象1Test b = new Test();//對象2a.object = b;b.object = a;a = null;b = null;}
}?
此時對象1和對象2除了對方指向自己的引用外,沒有其他的引用了,這個時候,無論是對象1還是對象2,我們認為都已經沒用了,因為程序是找不到它倆的,但是引用計數法無法將它們判定為垃圾,因為它們的被引用數不是為零
正是因為這個缺點,主流的java虛擬機都不會使用該判定方法
2、可達性分析:
選定一些滿足特定條件的對象作為根對象(GC Roots),那些與跟對象存在直接或間接引用關系的就是有用的對象,而與根對象沒有任何關聯的對象,就是垃圾對象(如下圖)
可達性分析是當今主流的判定機制
GC的分類
Minor GC是新生代GC,指的是發生在新生代的垃圾收集動作。由于java對象大都是朝生夕死的,所以Minor GC非常頻繁,一般回收速度也比較快。
Major GC是老年代GC,指的是發生在老年代的GC,通常執行Major GC會連著Minor GC一起執行。Major GC的速度要比Minor GC慢的多。
Full GC是清理整個堆空間,包括年輕代和老年代(Minor GC和Major GC一起執行就是Full GC)
GC和分代的關系
那么現在我們就知道了為什么要分代了:
對象實例一般會首先分配到新生代當中,當新生代當中的空間不夠用的時候,就會觸發Minor GC,這個時候就會有一些沒用的對象實例被清除掉,而有些就會留下來,那些能夠挺過一定次數Minor GC的對象,最后就會進入到老年代當中,如果老年代中的空間也不夠用了,那么就會進行Major GC
回收算法
我們上面說到GC會對垃圾進行回收,那具體要這么回收呢?這個就是回收算法,目前有三種回收算法,分別是:標記-清除、標記-復制、標記-整理
標記-清除
看下面的示意圖,這個代表堆中的某塊空間(可以是年輕代或老年代),每個紫色方塊就是一個對象,上面我們說,JVM的對象是否存活的判定方法是可達性分析,所有那些沒被GC Root引用的就要給標記成垃圾對象,標記完后再統一進行回收,這會造成內存空間碎片化的問題
?
標記-復制
將堆區分為兩塊區域,先只在其中一塊區域創建對象,垃圾回收的時候,先標記出那些不要被回收的對象,然后將其復制到另外一塊區域中,然后清空原本那塊區域,新生代使用的就是標記-復制算法,新生代分為一塊較大的Eden空間和兩塊較小的 Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾搜集時,將Eden和Survivor中仍然存活的對象一次性復制到另外一塊Survivor空間上,然后直接清理掉Eden和已用過的那塊Survivor空間。這里存在一個問題,當存活的對象的總大小大于那塊Survivor空間,那就會造成溢出,而那些溢出的對象,會直接進入老年代,這叫分配擔保
?標記-整理
標記-復制算法存在需要額外空間進行分配擔保的問題,新生代有老年代做分配擔保,那老年代沒人做分配擔保就沒辦法使用標記-整理算法,要使用標記-整理算法,同樣是先進行標記,不過不馬上進行回收,而是讓所有的存活對象都向內存空間一端移動,然后直接清理掉邊界以外的堆空間
標記-整理存在一個弊端,在整理的過程中,必須全程暫停用戶應用程序,這個被形象地稱為“Stop The World”,實際上只要對象的存儲地址發生了改變,就會“Stop The World”,所以標記-復制算法也會“Stop The World”
?
垃圾收集器
垃圾回收算法是理論層面,真正的垃圾回收執行者是垃圾收集器,關于垃圾收集器我整理了另外一篇博客,地址:http://t.csdn.cn/2ypGh