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

垃圾回收機制(1)

java中取消了指針操作,採用java虛擬機自動管理對象。Garbage Collection(GC)也就是所謂的垃圾回收器在java之前就得到了使用,1960年誕生的Lisp是第一門使用內存動態分配和垃圾回收的語言。

如何判斷對象是否存活。

(1)引用計數算法。(Jvm不用這個算法)
給對象添加一個引用計數器,用一個地方引用它,計數器值就+1,引用失效一個,計數器就減1.當對象的引用計數器值爲0則表示該對象已死亡,需要進行回收。但是JVM不用這個算法,當循環引用時,會產生問題。
例:

public class A{
public Object instance = null;
}
A a = new A();
A b = new A();
a.instance = b;
b. instance = a;
a = null;
b = null;
System.gc();

此時a對象跟b對象的引用計數值都爲1,引用計數算法就失效了。
(2)可達性分析算法
Java、C#等都採用這個算法判斷對象是否存活。將實例化的對象掛到GC Roots的路徑上,當一個對象到GC Roots沒有引用鏈時則被判斷爲對象已死亡(不可用)。這樣如果兩個對象互相引用,但是沒有達到GC roots的路徑,這兩個對象還是能對回收。java中,可作爲GC Roots的對象包括:堆棧中的本地變量表中引用的對象,方法去中靜態屬性引用的對象,常量引用的對象,本地方法棧中JNI引用的對象。

引用

我們知道reference是種引用類型,其中存着對象的內存地址。
java中分爲四種引用:強引用、軟引用、弱引用、虛引用。
(1)強引用:Object o = new Object()類似這種的就是強引用,只要強引用還在,那麼垃圾回收器就不會回收被引用的對象。
(2)軟引用:軟引用用來描述一些還有用但並必需的對象。在系統將要發生內存溢出異常之前,也就是內存不足時,會將這些對象列入回收範圍進行回收。jdk1.2之後,可以通過java.lang.ref.SoftReference類來表示軟引用。
例如:

SoftReference<Object> o = new SoftReference<Object>(new Object());

應用:可以應用到一些緩存上,當內存不夠時,對這些對象進行回收。
(3)弱引用:用來描述非必要對象,當下一次垃圾回收器進行回收時,就會被回收掉,不管內存是否足夠。通過java.lang.ref.WeakReference來引用對象。
例如:

WeakReference<Object> o = new WeakReference<Object>(new Object());

(4)虛引用:這是一種最弱的引用,無法獲得實例對象,唯一目的是當這個對象被回收時收到一個系統通知。用PhantomReference來表示,虛引用必須與引用隊列ReferenceQueue關聯
例如

        ReferenceQueue<String> queue = new ReferenceQueue<String>();
        PhantomReference<String> pr = new PhantomReference<String>(new String("lalal"), queue);
        System.out.println(pr.get());

當這個字符串對象將要被回收時,如果是個虛引用,那麼這個虛引用會被加到引用隊列中。如果發現引用隊列中有虛引用,那麼就知道這個對象要被回收了。

回收過程

如果一個對象是一個不可達對象,這個對象最終不一定會被回收。將要被回收的對象會有一個回收過程。真正宣告這個對象將被虛擬機回收會有兩個標記過程。先將這些可以被回收的對象進行第一次標記,然後進行一次刪選,將覆蓋了finalize()方法且這個方法沒有被調用過的對象放置到F-Queue隊列中,然後由一個低優先級的線程去執行這個隊列中對象的finalize()方法。在finalize這個方法中我們可以拯救這個對象,將這個對象掛到GC roots的路徑上,那麼這個對象就不會被回收。接着在第二次標記時將被拯救的對象移除要被回收的集合,然後還剩下的對象就會被回收。有一點要注意的是這個finalize方法只能被調用一次,也就是說只有一次被拯救的機會。而且我們需要避免去使用這個方法,這個方法的運行代價大,而且有不確定因素。

垃圾回收區的範圍

垃圾回收的範圍包括了,方法區跟堆區。雖然方法區被稱爲永久代,但是方法區中還含了常量池跟類的數據,方法區中無用的常量跟無用的類會被垃圾回收器回收。
滿足三個條件則被稱爲無用的類:
(1)該類所有的實例以及被回收。
(2)加載該類的ClassLoader已經被回收。
(3)該類的Class對象沒有被引用。

垃圾收集算法

(1)標記-清除算法
這是JVM中最基礎的收集算法。
顧名思義,該算法分爲標記與清除兩個步驟。
標記就是之前說的回收過程中的兩個標記過程,然後將可回收的內存空間進行回收。如下圖所示,我們可以看見在清除垃圾後,會留下許多內存碎片,所謂的內存碎片就是很小的內存空間。當需要給較大的對象分配內存空間時,則找不到足夠大的空間,那麼就又需要執行垃圾回收。標記算法清除垃圾的效率不高,因爲需要經過大量的標記,然後逐個將垃圾清除,同時內存碎片的問題還會經常觸發垃圾回收。
這裏寫圖片描述
(2)複製算法
複製算法是爲了解決“標記-清除算法”效率不高的問題,這個算法將內存容量劃分爲大小相等的兩塊,一塊作爲保留區域,一塊分配對象。當分配對象的區域不足以分配對象是,那麼將這塊區域存活的對象複製到保留區域上,然後將已使用過的內存空間一次清理掉。
這裏寫圖片描述
這種算法得缺點就時內存空間的利用率不高,因爲需要留下一半的空間來作爲保留區域。
現在的商業虛擬機採用這種垃圾收集算法來回收新生代。IBM公司研究表明,新生代中的對象98%是很快死亡,所以講新生代的內存分爲較大的Eden空間和兩塊較小的Servivor空間。HotSpot虛擬機的默認Eden區和Survivor區的比例是8:1,也就是說有兩塊Survivor區。回收時是將Eden區和Survivor區存活的對象複製到另一塊Survivor區,所以只有10%的內存空間被浪費。如果另一塊Survivor區不足以存放這些存活下來的對象,那麼將這些對象通過擔保機制存入老年代。
(3)標記-整理算法
複製算法會在對象存活率高的時候進行較多的複製複製操作,那麼效率會不高,而且如果不浪費50%的精簡就需要有額外的空間進行分配擔保。老年代對象的存活率較高,則提出了“標記-整理”算法。標記-清除算法和標記-整理算法的區別在於標記整理算法在回收的過程中不是對可回收對象進行清理,而是讓存活對象向前面移動,然後清理掉邊界以外的內存,這樣大大提升了清理的效率,也減少了內存碎片。
(4)分代收集算法
所謂的分代收集算法就是將java堆劃分爲新生代和老年代,新生代由於對象的產生和消亡很快,存活率不高,所以採用複製算法,老年代因爲對象存活率高,採用“標記-清理”或者“標記-整理”算法。

GC時刻

這裏主要講述HotSpot虛擬機的算法實現。
我們在做可達性分析時需要找出GC Roots的節點,GC Roots節點主要在全局性的引用與棧幀中的本地變量表內。虛擬機使用OopMap這個數據結構將對象引用的位置記錄下來。這樣我們可以根據OopMap中引用的信息來進行垃圾回收的可達性分析。
所以GC的時候,我們需要讓OopMap中的信息就是當前引用的信息,如果引用變化快,那麼OopMap中的內容就變得快,GC的空間成本會很高。這時候安全點就出現了。
(1)安全點:在特定的位置記錄下OopMap中的信息,這個點叫安全點,GC也是在這個時候進行執行。因爲在GC的時候需要所有引用不能變,所以需要停止線程,那麼這個安全點之間的距離不能太近,也不能太遠。選定是以“是否具有讓程序長時間執行的特徵”去判斷,程序的指令執行時間不會太長。那麼長時間的指令的特徵就是指令序列複用,如:方法調用、循環跳轉、異常跳轉,在這些時候會產生安全點。
(2)線程中斷:GC時需要線程中斷,那麼中斷的方式有兩種:一種是搶佔式中斷:就是GC發生時,主動將所有線程中斷,第二種是主動式中斷:GC發生時,如果有線程不在安全點上,那麼就讓線程跑到安全點上,再去中斷。
(3)安全區域:安全區域是一塊引用關係不會變化的區域,在這塊區域GC是安全的。如果線程在被掛起的時候,線程是無法跑到安全點的。所有才有了安全區域。只有完成了GC或者根節點枚舉,線程才能離開安全區。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章