java垃圾回收機制

  • 垃圾回收的意義
  • java中的垃圾回收機制
  • 對象在內存中的狀態
  • 探祕finalize()方法
  • 強引用、弱引用、軟引用、虛引用

垃圾回收的意義

也許是保護環境吧。

人類生活生產,總會產生各種各樣的垃圾,這些垃圾都怎麼處理?一般無外乎挑挑揀揀,分分類,能回收的當然好,回來洗洗消毒繼續用;不能回收的也沒辦法,埋了燒了?總之是對外界有一些不好的影響,這些不好的影響在程序猿看來,就是系統泄露,更具體一點就是內存泄漏了。

有過C/C++編程經歷的童鞋,都有一個很頭疼的問題:需要爲自己分配的內存負責,從生到死都得由自己來控制。一般說來,顯示地垃圾回收是一個比較蛋疼的事情,“我哪知道知道內存啥時候被釋放啊”,這估計也是大多數程序猿初識C++時的碎碎念。

不能回收的垃圾破壞了地球的生態系統,不能回收的內存造成計算機運行緩慢,嚴重一點系統就直接崩潰了。

總的來說,顯示回收有三個不太優雅的地方:

  • 回收不及時:對象的釋放時間不定,內存碎片的清理時間不定
  • 錯誤回收:一不小心回收了程序核心類庫佔用的內存,豈不是天塌地陷?

java中的垃圾回收機制

首先要明白,回收機制回收的是啥?

應該都知道,java中有棧內存和堆內存之別,棧內存主要存放一些變量的引用,而堆內存則是一個運行的數據區,類的實例一般都保存在這裏。JVM中提供了一個垃圾回收器——一種動態存儲管理技術,來管理這些堆內存,當一個jvm覺得一個對象不再有用或者內存塊之間有不少空閒區時,就會執行特定的回收算法來將其除掉。

相對C/C++來說,java的垃圾回收沒那麼事逼了,它不需要程序猿直接控制——所有的分配和回收都是由jre在後臺悄悄幫你辦好了,你可以干預,但最終回不回收,何時回收,怎麼回收還是由jvm說了算。一般的,jre會在後臺提供一個線程來監測和計算那些不再使用的內存,當CPU空閒或者內存不足時,回收機制就開始嶄露頭角了。

這樣,java程序猿就不用再考慮對象的存儲問題,編程效率自然上來,程序也更加美觀完整了。

當然,金無足赤,這樣優雅的實現依然有其弊端。許多事情表面上之所以風輕雲淡,那是因爲在你看不到的地方,有人默默幫你做了那些並不輕鬆的事情。jre提供了後臺的守護線程,默默地將我們丟下的每一片垃圾撿起、整理、重新分配給後來人。這樣的開銷勢必會影響程序的性能:jvm要區分出來程序中的有用和無用對象,勢必就要追蹤分析每一個對象的狀態;垃圾回收算法的不完善,也不能保證100%回收到所有的廢棄內存。

值得提出的是,java並沒有明確說明JVM使用哪種算法。有興趣研究的的參考一下這篇文章:http://blog.csdn.net/zsuguangh/article/details/6429592
但無論使用哪種算法,無外乎都有這些特點:

  • 只能回收堆內存中的對象,對任何物理資源(數據庫、網絡/磁盤IO等)無能爲力;
  • 精確的回收時間、回收方式,往往是不確定的。它依賴於具體的算法、具體的配置。
  • 能精確標記活着的對象和定位對象之間的引用關係,以免傷及無辜。
  • 回收任何對象之前,總會先調用它的finalize()方法,至於爲什麼,下面再討論。

對象在內存中的狀態

根據它被引用的狀態,可分爲三種:

  • 可達:這個沒啥說的;
  • 可恢復:不再有變量引用。但調用finalize()方法有可能讓一個變量重新引用該對象,則這個對象就會再次變爲可達狀態,否則就會變成不可達狀態;
  • 不可達:對象與所有引用的關聯已被切斷,調用finalize()也沒讓它復活,永久性地失去引用。只有對象處於不可達狀態時,系統纔會真正回收該對象所佔用的資源。

三者的關係圖如下:
對象狀態轉換示意圖

下面具體看個例子:

public class InitTest {

/**
 * @param args
 */
public static void main(String[] args) {
    // TODO Auto-generated method stub
    createObject();
}

public static void createObject() {
    String a = new String("測試");
    a = new String("演示");
}
}

當執行String a = new String(“測試”);時,”測試”對象處於可達狀態;執行a = new String(“演示”);時,”演示”對象處於可達狀態,而”測試”處於可恢復狀態,而並不是不可達狀態,系統此時也不會對它進行回收。有的童鞋不免要問,如果我此時強制讓它回收會怎麼樣呢?

強制回收

實際上強制回收的說法並不準確。前面已經提過,系統何時回收它所佔用的內存,程序根本不知道,程序能控制的只是保證一個對象何時不再被引用,或者通知系統進行垃圾回收,至於垃圾何時被回收,仍然由jvm來決定。

強制回收一般有兩種方式:

  • 調用System的gc()方法;
  • 調用Runtime的gc()方法;Runtime.getRuntime().gc()

探祕finalize()方法

java中垃圾回收繞不開的一個方法。它是一個對象被回收之前,最後一次可達的機會。當finalize()方法執行後,對象會消失,垃圾回收機制開始執行。

有過編程經驗的可能都注意到了,finalize()方法定義在Object類中,這就意味着所有的類都可以重寫它來實習自己的清理機制。如果一個程序終止之前都沒有執行垃圾回收,那麼finalize()方法則不會被調用。所以finalize()方法是否被調用也具有不確定性。

所以永遠不要在finalize()中清理資源,也永遠不要主動調用這個方法。看一下下面的例子:

public class FinalizeTest {
protected static FinalizeTest ft = null;

public void info() {
    System.out.println("測試finalize方法");
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    new FinalizeTest();
    // 通知系統進行資源回收
    // System.gc();
    System.runFinalization();//強制執行finalize()方法
    ft.info();
}

@Override
public void finalize() {
    ft = this;
}
}

上面的程序new FinalizeTest();之後,並未有任何引用,新創建的對象會進入可恢復狀態。調用System.gc(),並不能保證finalize()馬上被執行,如果註釋了System.runFinalization(),就會報空指針異常。

強引用、弱引用、軟引用、虛引用、引用隊列

垃圾回收機制中對對象的引用是一個很重要的判定標準。根據引用的級別(這一點和安卓中的視圖銷燬差不多),java中又可將引用分爲四種:

  • 強引用:毋庸贅述,最常見的引用方式,創建一個對象,把對象賦給一個引用變量,這樣的引用就是強引用。
  • 軟引用:SoftReference類實現,當一個對象只有軟引用時,它有可能被回收,如果系統內存不足的話。
  • 弱引用:WeakRefernce類實現,與上面的軟引用類似,只是引用級別更低。如果一個對象處於弱引用狀態,當垃圾回收機制運行,總是會回收該對象所佔用的內存,而不管內存是否充足。
  • 虛引用:PhantomReference類實現,級別最弱,類似於沒有引用。它的作用主要用來跟蹤對象被垃圾回收的狀態,無法單獨使用,必須陪護引用隊列。
  • 引用隊列:用來保存被回收後對象的引用。程序可檢查與虛引用關聯的引用隊列中是否包含了改虛引用,從而知道虛引用所指對象是否即將被回收。

瞅一眼下面的例子:

public class ReferenceTest {

/**
 * @param args
 */
public static void main(String[] args) {
    // TODO Auto-generated method stub
    String str = new String("引用測試");
    // 創建一個弱引用,引用到"引用測試"
    WeakReference weakReference = new WeakReference(str);
    // 斷開str和"引用測試"之間的引用
    str = null;
    // 打印弱引用對象
    System.out.println(weakReference.get());
    // 強制通知回收
    System.gc();
    System.runFinalization();
    // 再次打印弱引用對象
    System.out.println(weakReference.get());
}
}

輸入:
引用測試
null

再看一個虛引用的例子,兩者比較一下:

public class PhantomReferenceTest {

/**
 * @param args
 */
public static void main(String[] args) {
    // TODO Auto-generated method stub
    String str = new String("虛引用測試");
    // 創建一個引用隊列
    ReferenceQueue referenceQueue = new ReferenceQueue();
    // 創建一個指向"虛引用測試"的虛引用
    PhantomReference phantomReference = new PhantomReference(str,
            referenceQueue);
    // 斷開str和"虛引用測試"之間的引用
    str = null;
    System.out.println(phantomReference.get());
    // 強制回收
    System.gc();
    System.runFinalization();
    // 取出最先進入隊列的引用和phantomReference比較
    System.out.println(referenceQueue.poll() == phantomReference);
}
}

輸出:
null
true

再次強調幾點:

  • 強引用與其餘的中引用不能共存,如上例的“str=null”;
  • 虛引用不能單獨使用,所以PhantomReferenceTest 中會輸出null;
  • 強制回收後,虛引用的字符串被回收,對應的虛引用添加到關聯的引用隊列中,故最後輸出爲true。

先總結這麼多,有疑問的歡迎吐槽拍磚。

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