1. 程式人生 > >java中的物件與垃圾回收

java中的物件與垃圾回收

java的垃圾回收時java語言得重要功能之一。當程式建立物件、陣列等引用型別實體時,系統都會在堆記憶體中為之分配一塊記憶體區,物件就儲存在這塊記憶體區,當這塊記憶體區不在被任何引用變數所引用時,這塊記憶體就變成了垃圾,等待垃圾回收機制進行回收。

  • 垃圾回收機制只負責回收堆記憶體中的物件,不回收任何物理資源;
  • 程式無法精確控制垃圾回收執行,垃圾回收會在合適的時候進行。當物件永久性地失去引用後,系統會在合適的時候回收它所佔的記憶體。
  • 在垃圾回收機制回收任何物件之前,總先會呼叫它的finalize()方法,該方法可能使該物件重新復活,從而呆滯垃圾回收機制取消。

物件在記憶體中的狀態

在這裡插入圖片描述

package org.westos.Mydemo;

import java.lang.String

public class StatusTranfer {
    public static void test() {
        String a = new String("我愛java");//1
        
        a=new String ("java我愛你");//2
    }

    public static void main(String[] args) {
        test();
    }
}

定義了一個a變數並讓它指向"我愛java"物件,該段程式碼執行完畢後,"我愛java"變成可達狀態,當執行第2段程式碼後,a又指向"java我愛你"物件,第一個物件變成可恢復狀態,第二個物件變成可達狀態。

一個物件可以被一個方法的區域性變數引用,也可以被其他類的類變數引用,或被其他物件的例項變數引用。當某個物件被其他類的類變數引用時,只有當該類被銷燬後,該物件才會進入可恢復狀態;當某個物件被其他物件的實力變數引用時,只有當該物件被銷燬後,該物件會進入可恢復狀態。

強制垃圾回收

當一個物件失去引用後,系統何時呼叫finalize方法對它進行資源管理,何時它會變成不可達物件,系統何時回收它所佔有的記憶體,對於程式完全透明。程式只能控制一個物件何時不再被任何引用變數引用,決不能控制它何時被回收。
雖然系統不能控制時機,但依然可以強制系統進行垃圾回收,其實這種機制只是通知系統進行垃圾回收,但系統是否進行回收依然不確定。
兩種回收方式:

  • 呼叫System類的ge靜態方法:System.gc();
  • 呼叫Runtime物件的ge例項方法:Runtime.getRuntime().gc();
package org.westos.Mydemo;

public class gcdemo {

    public static void main(String[] args) {
        for (int i = 0; i <4 ; i++) {
            new gcdemo();
//            System.gc();
            Runtime.getRuntime().gc();
        }
    }

    public void finalize(){
        System.out.println("系統正在清理gcdemo物件的資源...");
    }
}

在這裡插入圖片描述

這種方式只是程式建議系統回收垃圾,系統完全有可能不會立即回收,垃圾回收機制會在收到通知後儘快進行處理。

finalize方法

java預設使用finalize方法來清理資源。
方法原型為:protected void finalize() throws Throwable
當這個方法執行後,物件消失,垃圾回收機制開始執行。throws Throwable表示它可以丟擲任何型別的異常。
如果程式在終止之前始終沒有進行垃圾回收,則不會呼叫失去引用物件的finalize方法來清理資源。只有當程式認為需要更多的額外記憶體時,垃圾回收機制才會進行垃圾回收。

finalize方法的特點:

  • 永遠不要主動呼叫某個物件的finalize方法,該方法應該交給垃圾回收機制呼叫;
  • finalize方法的呼叫具有不確定性;
  • 當JVM執行可恢復物件的finalize方法時,可能使該物件或系統中的其他物件重新變成可達狀態;
  • 當JVM執行finalize方法時出現異常時,垃圾回收機制不會報告異常;

演示如何在finalize方法裡復活自身:

package org.westos.Mydemo;

public class FinalizrTest {
    private static FinalizrTest ft=null;
    public void info(){
        System.out.println("測試資源管理的finalize方法");
    }

    public static void main(String[] args) {
        //建立FinalizrTest物件立即進入可恢復狀態、
        new FinalizrTest();
        //通知系統進行資源回收
        System.gc();//1
        //垃圾回收機制呼叫可恢復物件的finalize方法
//        Runtime.getRuntime().runFinalization();//2
        System.runFinalization();//3
        ft.info();
    }
    public void finalize(){
        //讓ft引用到試圖回收可恢復物件
        ft=this;
    }
}

上面程式碼重寫了finalize方法,讓物件在方法中復活;如果呼叫了2、3程式碼可以看到下列執行結果,也就是說finalize方法立即被執行了,物件隨之復活正常呼叫info方法;

在這裡插入圖片描述

如果將2,3程式碼註釋掉,會發現有時候能呼叫到info方法,有時候會出現下列的空指標異常現象,這就是垃圾回收機制並沒有立即回收物件的結果,也就是沒有及時呼叫finalize方法
在這裡插入圖片描述

物件的軟、弱和虛引用

對大部分物件而言,程式裡會有一個引用變數引用該物件,這是最常見的方式。

java中有四種引用方式:

  • 強引用(StrongReference):這種方式是最常見的,程式建立一個物件,並把這個物件賦給一個引用變數,程式通過該引用變數來操作實際的物件。
  • 軟引用(SoftReference):需要通過SoftReference類來實現,當一個物件只有軟引用時,他有可能被垃圾回收機制回收,對於只有軟引用的物件而言,當系統記憶體空間足夠時,他不會被回收,程式也可使用該物件,當空間不足時,系統可能會回收它;
  • 弱引用(WeakReference):通過WeakReference類實現,弱引用和軟引用很像,但是它的級別更低。當系統垃圾回收機制執行時,不管系統記憶體是否足夠,總會被回收。
  • 虛引用(PhantomReference):通過PhantomReference類來實現,類似於沒有引用,主要用於跟蹤物件被垃圾回收的狀態,不能單獨使用,必須和引用佇列聯合使用。

引用佇列由java.lang.ref.ReferenceQueue類表示,它用於儲存被回收後物件的引用。

package org.westos.demo3;

import java.lang.String;
import java.lang.ref.WeakReference;

public class ReferenceTest {
    public static void main(String[] args) {
        //建立一個字元物件
        String str = new String("我愛java");

        //建立一個弱引用並且引用到字串
        WeakReference wr = new WeakReference(str);
        //切斷引用
        str=null;
        //取出弱引用所引用的物件,雖然已經切斷了引用,但是記憶體空間不緊張,索引物件未被回收
        System.out.println(wr.get());//我愛java
        //強制垃圾回收
        System.gc();
        System.runFinalization();
        //再次取出弱引用所引用的物件,由於已經物件已經被回收,所以輸出null
        System.out.println(wr.get());//null

    }
}

用途

  • 可以避免在程式執行期間將物件留在記憶體中;
  • 如果以軟引用、弱引用、虛引用來引用物件,垃圾回收器就能夠碎一地釋放物件,如果希望儘可能減小程式在其宣告週期中所佔用的記憶體大小時,這些引用類就很有用。