1. 程式人生 > >深入理解java虛擬機(二)-----垃圾回收

深入理解java虛擬機(二)-----垃圾回收

引用 對象實例 一件事 jdk1 nor str 引用計數 系統通知 native

做一個java程序員很是幸福,不用管不用的對象如何被回收,但是我認為了解一下也不是壞事。

一、如何判斷對象已經死亡?

在進行垃圾回收之前,第一件事肯定是判斷對象是否已經死亡。
1、引用計數算法
給對象添加一個引用計數器,當程序中使用到這個對象的時候,計數器+1;如果引用失效,計數器-1,當計數器為0時,說明程序中不再使用這個對象,既可以回收。
問題:試想一下,當A對象中使用B對象,B對象中有方法使用A對象,完犢子,兩個對象永遠存在。

2、可達性分析算法(主流的商用程序語言的主流實現,都是基於此算法)
通過一系列GC Roots的對象作為起始點,當一個對象到GC Roots沒有任何引用鏈相連,說明對象不可用,可以被回收。

技術分享圖片

那麽GC Roots對象的選擇非常重要,主要包括下面幾種:
1、虛擬機棧中引用的對象
2、方法區中類靜態屬性引用的對象
3、方法區中常量引用的對象
4、本地方法棧中JNI(native方法)引用的對象

二、引用

在jdk1.2之前,引用是這麽定義的:
如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表著一個引用。
在jdk1.2之後,對引用進行了擴展:
1、強引用
Student student = new Student(),這種就叫做強引用,這種只要引用存在,垃圾收集器永遠不會回收對象。
2、軟引用
有用但不是必需的對象,在系統將要發生內存溢出異常之前,把這些對象列進回收範圍進行第二次回收。SoftReference表示軟引用。

3、弱引用
非必需的對象,強度比軟引用更弱一些,弱引用對象只能生存到下一次垃圾收集發生之前。WeakReference表示弱引用。
4、虛引用
無法通過虛引用取得一個對象實例,存在的唯一意義就是在這個對象被收集器回收時收到一個系統通知。PhantomReference表示虛引用。

三、垃圾回收算法

垃圾回收的算法有很多,逐一介紹。
1、標記-清除算法
首先通過上面介紹的GC Roots算法判斷對象是否存活,然後將不存活的對象打上標記。如下:

技術分享圖片

技術分享圖片

缺點:

  • 效率問題,標記和清除兩個過程的效率都不算高
  • 資源浪費問題:從圖中可以看到,清除完之後的內存都是不連續的,產生了很多的內存碎片,這樣以後內存分配其他大對象的時候,就無法分配了。

2、復制算法

復制算法將內存分為大小相等的兩塊,每次只使用一塊,當一塊內存快滿的時候,就將還存活的對象復制到另外一塊內存上,然後把已使用的內存一次性清理掉,這樣會提高效率。缺點則是浪費內存了,一次只能使用一半。

技術分享圖片

技術分享圖片

大部分的商業虛擬機都是采用這種算法還回收新生代,在之前的博文中提到了新生代主要可以分為eden、from survivor、to survivor,
通常eden:survivor=8:1,因為大部分新生代中的對象,大概能有98%是朝生夕死的。

3、標記-整理算法

標記過程基於標記-清除算法,但是整理的時候則不一樣,讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。

技術分享圖片

技術分享圖片

4、分代收集

以上幾種算法各有各的特點,當前主流的商業虛擬機對於不同的內存區域,例如新生代、老年代,不同區域的對象特點不同,所以使用不同的算法達到最佳的效果。

四、內存分配

1、對象優先在eden區分配,如果eden區內存不夠,則觸發一次minor gc。
minor gc:年輕代內存回收被稱為minor gc。
major gc:清理老年代。
full gc:清理整個堆空間—包括年輕代和老年代。

2、大對象直接進入老年代
大對象:需要大量連續內存空間的java對象,例如很長的字符串以及數組對象。


3、長期存活的對象將進入老年代
如何判斷對象是長期存活的,其實也簡單,jvm給對象維護了年齡計數器。對象從eden區沒有被回收掉,年齡+1,到了survivor區還是沒被幹掉,年齡+1,達到年齡的閾值後,放入到老年代。

深入理解java虛擬機(二)-----垃圾回收