1. 程式人生 > 實用技巧 >Java引用型別之弱引用與幻像引用

Java引用型別之弱引用與幻像引用

這一篇將介紹弱引用和幻像引用。

1、WeakReference

WeakReference也就是弱引用,弱引用和軟引用類似,它是用來描述"非必須"的物件的,它的強度比軟引用要更弱一些。被弱引用關聯的物件只能生存到下一次垃圾收集發生之前,簡言之就是:一旦發生GC必定回收被弱引用關聯的物件,不管當前的記憶體是否足夠。WeakReference類的定義如下:

public class WeakReference<T> extends Reference<T> {	
    public WeakReference(T referent) {	
        super(referent);	
    }	
    public WeakReference(T referent, ReferenceQueue<? super T> q) {	
        super(referent, q);	
    }	
}

WeakReference繼承了Reference。在ReferenceProcessorStats ReferenceProcessor::process_discovered_references()方法中呼叫process_discovered_reflist()方法處理弱引用,如下:

// Weak references
size_t weak_count = 0;
{
    // 傳遞的clear_referent的值為true
    weak_count =  process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
}

呼叫的process_discovered_reflist()方法中會用後2個階段處理弱引用,在之前已經介紹過,這裡不再介紹。

2、PhantomReference

PhantomReference也就是幻像引用,它是所有引用型別中最弱的一種。一個物件是否關聯到虛引用,完全不會影響該物件的生命週期,也無法通過虛引用來獲取一個物件的例項。為物件設定一個虛引用的唯一目的是:能在此物件被垃圾收集器回收的時候收到一個系統通知。PhantomReference類的定義如下:

public class PhantomReference<T> extends Reference<T> {	
    public T get() {	
        return null;	
    }	
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {	
        super(referent, q);	
    }	
}

可以看到幻像引用的get()方法永遠返回null,所以也就無法通過虛引用來獲取一個物件的例項。

public static void demo() throws InterruptedException {	
        Object                    obj = new Object();	
        ReferenceQueue<Object>    refQueue =new ReferenceQueue<>();	
        PhantomReference<Object>  phanRef =new PhantomReference<>(obj, refQueue);	
        Object                    objg = phanRef.get();	
        // 這裡拿到的是null	
        System.out.println(objg);	
        // 讓obj變成垃圾	
        obj=null;	
        System.gc();	
        Thread.sleep(3000);	
        // GC後會將phanRef加入到refQueue中	
        Reference<? extends Object> phanRefP = refQueue.remove();	
         //這裡輸出true	
        System.out.println(phanRefP==phanRef);	
}

從以上程式碼中可以看到,幻像引用能夠在指向物件不可達時得到一個“通知”(其實所有繼承Reference的類都有這個功能),需要注意的是GC完成後,phanRef.referent依然指向之前建立Object,也就是說Object物件一直沒被回收!

而造成這一現象的原因在之前也介紹過: 對於Finalreference和Phantomreference來說,clear_referent 欄位傳入的值為false,意味著被這兩種引用型別引用的物件,如果沒有其他額外處理,在GC中是不會被回收的。

對於幻像引用來說,從refQueue.remove()得到引用物件後,可以呼叫clear()方法強行解除引用和物件之間的關係,使得物件下次可以GC時可以被回收掉。

在ReferenceProcessor::process_discovered_references()方法中呼叫process_discovered_reflist()方法處理幻像引用,如下:

// Phantom references
size_t phantom_count = 0;
{
    // 傳遞的clear_referent的值為false
    phantom_count =  process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
                                 is_alive, keep_alive, complete_gc, task_executor);
}

呼叫的process_discovered_reflist()方法中會用後2個階段處理幻像引用,在之前已經介紹過,這裡不再介紹。

3、Cleaner

PhantomReference有兩個比較常用的子類,如下:

(1)java.lang.ref.Cleaner:開發者用於在引用物件回收的時候觸發一個動作,在JDK 9中將完全替代Object.finalize()方法。

(2)jdk.internal.ref.Cleaner:用於DirectByteBuffer物件回收的時候對於堆外記憶體的回收。ReferenceHandler執行緒會對pending連結串列中的jdk.internal.ref.Cleaner型別引用物件呼叫其clean()方法。

對於java.lang.ref.Cleaner類來說,舉個例子如下:

import java.lang.ref.Cleaner;
 
public class CleaningExample implements AutoCloseable {
	// A cleaner, preferably one shared within a library
	private static final Cleaner cleaner = Cleaner.create();
 
	static class State implements Runnable {
 		State() {
			System.out.println("init");// initialize State needed for cleaning action
		}
 
		public void run() {
			System.out.println("clean");// cleanup action accessing State, executed at most once
		}
	}
 
	private final State state;
	private final Cleaner.Cleanable cleanable;
 
	public CleaningExample() {
		this.state = new State();
		this.cleanable = cleaner.register(this, state);
	}
 
	public void close() {
		cleanable.clean();
	}
	
	public static void main(String[] args) {
		while(true) {
			new CleaningExample();
		}
	}
}

本例中每次建立物件時,都會列印init;回收物件時,都會列印clean。  

下面介紹jdk.internal.ref.Cleaner類。

Cleaner繼承自Java四大引用型別之一的幻像引用PhantomReference(眾所周知,無法通過幻像引用獲取與之關聯的物件例項,且當物件僅被幻像引用引用時,在任何發生GC的時候,其均可被回收),通常PhantomReference與引用佇列ReferenceQueue結合使用,可以實現幻像引用關聯物件被垃圾回收時能夠進行系統通知、資源清理等功能。如下圖所示,當某個被Cleaner引用的物件將被回收時,JVM垃圾收集器會將此物件的引用放入到物件引用中的pending連結串列中,等待ReferenceHandler進行相關處理。其中,ReferenceHandler為一個擁有最高優先順序的守護執行緒,會迴圈不斷的處理pending連結串列中的物件引用,執行Cleaner的clean()方法進行相關清理工作,這在之前介紹ReferenceHandler類時介紹過,這裡不再介紹。

Cleaner只有在清理邏輯足夠輕量和直接的時候才適合使用Cleaner,繁瑣耗時的清理邏輯將有可能導致ReferenceHandler執行緒阻塞從而耽誤其它的清理任務。

相關文章的連結如下:

1、在Ubuntu 16.04上編譯OpenJDK8的原始碼

2、除錯HotSpot原始碼

3、HotSpot專案結構 

4、HotSpot的啟動過程

5、HotSpot二分模型(1)

6、HotSpot的類模型(2)

7、HotSpot的類模型(3)

8、HotSpot的類模型(4)

9、HotSpot的物件模型(5)

10、HotSpot的物件模型(6)

11、操作控制代碼Handle(7)

12、控制代碼Handle的釋放(8)

13、類載入器

14、類的雙親委派機制

15、核心類的預裝載

16、Java主類的裝載

17、觸發類的裝載

18、類檔案介紹

19、檔案流

20、解析Class檔案

21、常量池解析(1)

22、常量池解析(2)

23、欄位解析(1)

24、欄位解析之偽共享(2)

25、欄位解析(3)

26、欄位解析之OopMapBlock(4)

27、方法解析之Method與ConstMethod介紹

28、方法解析

29、klassVtable與klassItable類的介紹

30、計算vtable的大小

31、計算itable的大小

32、解析Class檔案之建立InstanceKlass物件

33、欄位解析之欄位注入

34、類的連線

35、類的連線之驗證

36、類的連線之重寫(1)

37、類的連線之重寫(2)

38、方法的連線

39、初始化vtable

40、初始化itable

41、類的初始化

42、物件的建立

43、Java引用型別

44、Java引用型別之軟引用(1)

45、Java引用型別之軟引用(2)

46、Java引用型別之弱引用與幻像引用

作者持續維護的個人部落格classloading.com

關注公眾號,有HotSpot原始碼剖析系列文章!

 

參考文章:

Java魔法類:Unsafe應用解析