電腦程式設計中,弱引用強引用相對,是指不能確保其引用的對象不會被垃圾回收器回收的引用。一個對象若只被弱引用所引用,則被認為是不可訪問(或弱可訪問)的,並因此可能在任何時刻被回收。一些配有垃圾回收機制的語言,如JavaC#PythonPerlLisp等都在不同程度上支持弱引用。

垃圾回收

垃圾回收用來清理不會再使用的對象,從而降低內存洩露和數據損壞的可能性。垃圾回收主要有兩種類型:追蹤引用計數。引用計數會記錄給定對象的引用個數,並在引用個數為零時收集該對象。由於一次僅能有一個對象被回收,引用計數無法回收循環引用的對象。一組相互引用的對象若沒有被其它對象直接引用,並且不可訪問,則會永久存活下來。一個應用程式如果持續地產生這種不可訪問的對象群組,就會發生內存洩漏。在對象群組內部使用弱引用(即不會在引用計數中被計數的引用)有時能避免出現引用環,因此弱引用可用於解決循環引用的問題。如Apple的Cocoa框架就推薦使用這種方法,具體為,在父對子引用時使用強引用,子對父引用時使用弱引用,從而避免了循環引用。[1]頁面存檔備份,存於網際網路檔案館

程序對一些對象只進行弱引用,通過此法可以指明哪些對象是不重要的,因此弱引用也用於儘量減少內存中不必要的對象存在的數量。

變種

有些語言包含多種強度的弱引用。例如Java,在java.lang.ref[1][2]包中定義了軟引用、弱引用和虛引用,引用強度依次遞減。每種引用都有相對應的可訪問性概念。垃圾回收器(GC)通過判斷對象的可訪問性類型來確定何時回收該對象。當一個對象是軟可訪問的,垃圾回收器就可以安全回收這個對象,但如果垃圾回收器認為JVM還能空出可用內存(比如JVM還有大量未使用的堆空間),則有可能不會立刻回收軟可訪問的對象。但對於弱可訪問的對象,一旦被垃圾回收器注意到,就會被回收。和其他引用種類不同,虛引用無法跟蹤。但另一方面,虛引用提供了一種機制,當一個對象被回收時程序可以得到通知(實現於ReferenceQueues[3])。 一些未配有垃圾回收機制的語言,比如C++,也提供強/弱引用的功能,以作為對垃圾回收庫的支持。在C++中,普通指針可看做弱引用,智能指針可看做強引用,儘管指針不能算"真正"的弱引用,因為弱引用應該能知道何時對象變成不可訪問的了。

示例

弱引用可用於在應用程式中維護一個當前被引用的對象的列表。該列表必須弱引用到那些對象,否則一旦對象被添加到列表中,由於它們被列表引用了,在程序運行期間將永遠不會被回收。

Java

Java是第一個將強引用作為默認對象引用的主流語言。之前的(ANSI)C語言只支持弱引用。而後David Hostettler Wain和Scott Alexander Nesmith注意到事件樹無法正常釋放的問題,結果在大約1998年,推出了分別會被計數和不會被計數的強、弱引用。 如果創建了一個弱引用,然後在代碼的其它地方用 get()獲得真實對象,由於弱引用無法阻止垃圾回收,get()隨時有可能開始返回 null(假如對象沒有被強引用)。[4]

import java.lang.ref.WeakReference;
 
public class ReferenceTest {
	public static void main(String[] args) throws InterruptedException {
 
            WeakReference r = new WeakReference(new String("I'm here"));
            WeakReference sr = new WeakReference("I'm here");
            System.out.println("before gc: r=" + r.get() + ", static=" + sr.get());
            System.gc();
            Thread.sleep(100);
 
            //只有r.get()变为null
            System.out.println("after gc: r=" + r.get() + ", static=" + sr.get());
 
	}
}

弱引用還可以用來實現緩存。例如用弱哈希表,即通過弱引用來緩存各種引用對象的哈希表。當垃圾回收器運行時,假如應用程式的內存占用量高到一定程度,那些不再被其它對象所引用的緩存對象就會被自動釋放。

Smalltalk

|a s1 s2|

s1 := 'hello' copy. "这是个强引用"
s2 := 'world' copy. "这是个强引用"
a := WeakArray with:s1 with:s2.
a printOn: Transcript. 
ObjectMemory collectGarbage.
a printOn: Transcript. "两个元素都还在"

s1 := nil. "移除强引用" 
ObjectMemory collectGarbage.
a printOn: Transcript. "第一个元素消失"

s2 := nil. "移除强引用" 
ObjectMemory collectGarbage.
a printOn: Transcript. "第二个元素消失"

Lua

weak_table = setmetatable({}, {__mode="v"})
weak_table.item = {}
print(weak_table.item)
collectgarbage()
print(weak_table.item)

Objective-C 2.0

在Objective-C 2.0中,除了垃圾回收,自動引用計數也會受弱引用的影響。下面這個例子中的所有變量和屬性都是弱引用。

@interface WeakRef : NSObject
{
    __weak NSString *str1;
    __assign NSString *str2;
}
 
@property (nonatomic, weak) NSString *str3;
@property (nonatomic, assign) NSString *str4;
 
@end

weak(__weak)和assign(__assign)的區別在於,當變量指向的對象被重新分配時,變量的值是否會跟著改變。weak聲明的變量會變為nil,而assign聲明的變量則會保持不變,成為一個懸擺指針。從Mac OX 10.7 「獅子」系統和iOS 5開始,隨著Xcode 4.1版本的推出,weak引用被引入到Objective-C語言中(4.2版本開始支持iOS)。老版本的Mac OS X、iOS和GNUstep僅支持用assign聲明弱引用。

Vala

class Node {
    public weak Node prev; //弱引用可避免列表中的节点之间出现循环引用
    public Node next;
}

Python

>>> import weakref
>>> import gc
>>> class Egg:
...     def spam(self):
...         print("I'm alive!")
...
>>> obj = Egg()
>>> weak_obj = weakref.ref(obj)
>>> weak_obj().spam()
I'm alive!
>>> obj = "Something else"
>>> gc.collect()
35
>>> weak_obj().spam()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'spam'

參考文獻

  1. ^ java.lang.ref. [2013-12-28]. (原始內容存檔於2022-06-06). 
  2. ^ Nicholas, Ethan. "Understanding Weak References" 網際網路檔案館存檔,存檔日期2010-08-19.. java.net頁面存檔備份,存於網際網路檔案館). 發布於2006年5月4日. 訪問於2010年10月1日
  3. ^ ReferenceQueues. [2013-12-28]. (原始內容存檔於2011-09-30). 
  4. ^ weblogs.java.net Java Examples 網際網路檔案館存檔,存檔日期2010-08-19.