Java 虛擬機的堆裏存放着程序運行中所建立的全部對象。虛擬機可使用new
、newarray
、anewarray
和multianewarray
指令來建立對象,可是沒有明確的代碼來釋放它們。垃圾收集
就是自動釋放再也不被程序所使用的對象的過程。java
本篇文章並非要描述正式的 Java 垃圾收集器,由於根本不存在這樣一個正式的描述。前面說過,Java 虛擬機規範不要求任何特定的垃圾收集技術,這根本不是必需的。可是在發明能夠無限使用的內存前,大部分的 Java 虛擬機都會附帶垃圾收集功能。程序員
垃圾收集
這個名字暗示着程序再也不須要的對象就是垃圾
,能夠被丟棄。更準確地說,應該被叫作內存回收
。當一個對象再也不被程序所引用時,它所使用的堆空間
能夠被回收,一遍後續的新對象所使用。算法
垃圾收集器必須可以確認哪些對象是再也不被引用的,而且可以把它們所佔據的堆空間釋放出來。在釋放對象的過程當中,垃圾收集器還要運行將要被釋放對象的終結方法(finalizer
)。數據庫
除了釋放再也不被引用的對象,垃圾收集器還要處理堆碎塊。堆碎塊是在程序運行過程當中產生的。在請求分配新對象的內存空間時,可能不得不增大堆空間的大小,雖然可使用的總空閒空間是足夠的。這是由於堆中的空閒空間並不連續,沒法放下一個新的對象。編程
把內存回收的任務交給虛擬機有幾個好處:緩存
可是使用垃圾收集,有一個潛在的缺陷就是加大了程序的負擔,可能影響程序性能。Java虛擬機必須追蹤哪些對象正在使用,哪些對象須要釋放。和明確釋放內存比起來,內存釋放過程還會須要更多的CPU時間片。安全
任何垃圾收集算法必須作兩件事:網絡
垃圾檢測一般經過創建一個根對象
的集合而且檢查從這些跟對象開始的可觸及性來實現。若是正在執行的程序能夠訪問到跟對象和某個對象之間存在引用路徑,這個對象就是可觸及的。對於程序來講,跟對象老是能夠訪問的。從這些根對象開始,任何能夠被觸及的對象都被認爲是活動
的對象。沒法被觸及的對象被認爲是垃圾
,它們再也不被程序使用到。數據結構
任何根對象引用的對象都是可觸及的,從而是活動的。另外,任何被活動的對象引用對象都是可觸及的。程序能夠訪問任何可觸及的對象,因此這些對象必須保存在堆裏面。任何不可觸及的對象均可以被收集,由於程序被辦法訪問它們。jvm
Java 虛擬機的根對象集合根據實現方式各有不一樣,可是總會包含局部變量中的對象引用和棧幀的操做數棧(以及類變量中的對象引用)。關於根對象的來源大概有這幾種:
說實話這三條咋這麼抽象嘞,先往下看看把
在 Java 虛擬機的實現中,有些垃圾收集器能夠區分真正的對象引用和看上去很像合法對象引用的基本類型(好比一個 int 變量)之間的差異。(例如一個 int 整數,若是被解釋是一個本地指針,可能指向堆中的一個對象)但是某些垃圾收集器仍然選擇不區分真正的對象引用和假裝品
,這種垃圾收集器被稱爲保守的,由於它們可能作不到釋放掉每個再也不引用的對象。對於保守的收集器,有時候垃圾對象也被錯誤的判斷爲活動的,由於有一個看上去像是對象引用的基本類型"引用"了對象。這種保守的垃圾收集器是垃圾回收速度提升了,由於有一些垃圾被遺忘了。
區分活動對象和垃圾對象的兩個基本方法是引用計數和跟蹤。
引用計數是垃圾回收的早期策略。在這種方法中,堆中每個對象都有一個引用計數。
規則包括:
在這種方法中,一個對象被垃圾收集後可能會觸發後續其餘對象的垃圾收集行動。
引用計數沒法檢測出循環引用(即兩個或者更多對象之間的相互引用)。
循環引用的例子如
class A{
public B b;
}
class B{
public A a;
}
public class Main{
public static void main(String[] args){
A a = new A();
B b = new B();
a.b=b;
b.a=a;
a=null;
b=null;
}
}
複製代碼
a 和 b 雖然置爲了 null,可是按照引用計數的規則永遠不會被收集,由於 a 和 b 分別持有各自的引用。
跟蹤收集器是追蹤從根節點開始的對象引用圖。在追蹤過程當中遇到的對象以某種方式打上標記。標記時,要麼在對象自己設置標記,要麼用一個獨立的位圖來設置標記。當追蹤結束時,未被標記的對象就知道是沒法觸及的,從而能夠被收集。
基本的追蹤算法被稱做標記-清除
算法。這個名字指出了垃圾收集過程的兩個階段:
Java虛擬機的垃圾收集器可能有對付堆碎塊的策略。標記-清除
收集器一般使用的兩種策略是壓縮和拷貝。這兩種方式都是經過快速地移動對象來減小堆碎塊。
壓縮收集器把活動的對象越過空閒區移動到堆的另外一端,這樣堆的另外一端會出現一個大的連續空間。此後,全部被移動的對象的引用也會被更新,指向新的內存地址。
更新被移動對象的引用有時候會經過一個間接對象引用層
,不直接引用堆中的對象,對象的引用實際上指向一個對象句柄表。對象句柄纔是真正指向堆中對象的實際位置。當對象被移動了,只須要更新句柄表就能夠。不過在對象的訪問上,由於增長了一個句柄表,性能有所損失。
拷貝垃圾收集器把全部的活動對象都移動到一個新的區域。在拷貝的過程當中,它們被緊挨着佈置,因此能夠消除本來它們在舊區域的空隙。而原有的區域被認爲是空閒區。
這種方法的好處就是從根對象開始遍歷的過程當中,一旦發現對象就進行拷貝,再也不有標記和清除的區分。對象被快速拷貝到新區域,同時轉向指針
仍然留在原來的位置。轉向指針
可讓垃圾收集器發現已經被轉移對象的引用。而後垃圾收集器把和這個對象有關的引用設置爲轉向指針
的值。
通常的拷貝收集器算法被稱爲中止-拷貝
。方案以下:
中止-拷貝
過程結束後,程序恢復執行。這種方法的代價就是,對於指定大小的堆來講,實際上須要兩倍的內存來運行。
圖形描述以下:
上圖一共是9張堆內存快照:在程序執行中,這個上述過程一次有一次地重複。
簡單的中止-拷貝
收集器的缺點是,每一次收集時,全部的活動對象都必須被拷貝。大部分語言的大多數程序都有一下特色,若是咱們全面考慮這些,拷貝算法是能夠改進的。
簡單的中止-拷貝
收集器浪費效率的一個主要緣由就是,它們每次把這些生命週期很長的對象來回拷貝,消耗大量的時間。
按代收集的收集器經過把對象按照壽命來分組解決中止-拷貝
效率低下的問題,更多地收集那些短暫出現的年幼對象,而非壽命比較長的對象。邏輯以下:
按代收集技術除了能夠應用於中止-拷貝
垃圾收集算法,也能夠用於標記-清除
垃圾回收算法。無論哪一種狀況下,把堆按照年齡層分解均可以提升最基本的垃圾收集算法的性能。
自適應收集器算法李永樂以下事實:在某種狀況下某些垃圾收集算法工做的更好,而另一些收集算法在另外的狀況下工做得更好。
自適應算法監視堆中的情形,並對應的調整爲合適的垃圾收集技術。
使用自適應方法,Java虛擬機的實現者不須要只選擇一種特定的算法。可使用多種技術,以便在最擅長的場合使用它們。
train GC
)火車算法最先是有Richard Hudson
和Eliot Moss
提出的,目的是爲了在成熟對象空間提供限定時間的漸進收集
,最先用於Sun公司的Hotspot
虛擬機。該算法詳細的說明了按代收集的垃圾收集器的成熟對象空間的組織。
唏噓的是到Sun JDK 6的時候就已經完全不包含train GC了,不過更重要的是思想,仍是看一看吧。
垃圾收集算法和主動釋放對象內存比起來有一個潛在的缺點,即垃圾收集算法中程序員對安排 CPU 時間進行內存回收的過程缺少控制。
要精確的預測出什麼時候進行垃圾收集、收集須要多長時間基本上是不可能。由於垃圾收集通常會停止整個程序來查找和收集垃圾對象,它們可能在程序執行的任意時刻觸發垃圾收集,而且停止的時間也沒法肯定。這種垃圾收集的暫停有時候長得讓用戶注意到了。
而當一種垃圾收集算法可能致使用戶可察覺到的停頓或者使得程序沒法知足實時系統的要求,這種算法被稱做破壞性的。
達到非破壞性垃圾收集的方法是使用漸進式垃圾收集算法。
漸進式垃圾收集器就是不試圖一次性發現並回收全部的垃圾對象,而是每次發現並回收一部分。所以每次都只有堆的一部分執行垃圾收集,所以理論上說的每一次收集會持續更短的時間。
若是可以保證每次收集不超過一個最大時間長度,就可讓Java虛擬機適合實時環境,而且也能夠消除用戶可察覺的停頓。
一般漸進式收集器都是按代收集的收集器。
火車算法把成熟的對象空間劃分爲固定長度的內存塊,算法每次在一個塊中單獨執行。規則以下:
在原始論文中,內存塊叫作車箱
;集合叫作火車
。成熟對象的內存空間叫作火車站
。
算法組織圖以下:
火車按照它們建立時的順序分配號碼。
所以,假設咱們將第一列火車(最早進入該年齡層的對象內存)被拉進軌道1,稱爲火車1。到達的第二輛火車被拉到軌道二,稱爲火車2。下一列到達的火車被拉到軌道3。
依次類推,按照這樣的計劃,號碼較小的火車老是更早出現的火車。
在火車內部,車箱(內存塊)老是附加到火車的尾部。
附加的第一節車箱被稱爲車箱1,這列車附加的下一節車箱被稱爲車箱2。 所以,在列車內部,較小的數字總能表示更早出現的車箱。
這個命名計劃給出了成熟對象空間中內存塊的整體順序。
上圖中顯示了三列車,標記爲火車1
、火車2
、火車3
。
火車1
擁有四節車箱,標記爲 1.1-1.4火車2
擁有三節車箱,標記爲 2.1-2.3火車3
擁有五節車箱,標記爲 3.1-3.5而對於加入的順序爲:
火車1
的最後一節車箱老是在火車2
的第一節車箱前面,因此車箱1.4在車箱2.1以前。同理,車箱2.1在車箱3.1以前。火車算法每一次執行的時候,只會對一個塊(號碼最低的塊)執行垃圾收集。對於上圖,它會收集車箱1.1,下次執行時會收集車箱1.2。當它收集了火車1
的最後一個車箱,算法在下一次執行時收集火車2
的車箱2.1。(從這部分看,在收集完一個車箱後,算法應該是要把收集過的車箱移走)。
對象從更年輕的年齡層的子堆進入成熟對象空間,無論什麼時候進入,它們都會被附加到任何已經存在的火車中(最小數字的火車除外),或者專爲容納它們而創建的一列或多列火車中。也就是說,對象有兩種方法到達火車站:
==最小數字的火車除外==是爲何呢?
由於算法始終檢測的是最小數字的火車
,或者最小數字的火車
的最小數字的車箱
。這列火車不會直接存放剛剛進入火車站的對象。看下面的車箱收集
就明白啦!
每一次算法被執行的時候,它要麼收集最小數字火車
中的最小數字車箱
,要麼收集整列最小數字火車
。思路以下:
最小數字火車
中任何車箱的引用,若是不存在任何來自最小數字火車
外的引用指向最小數字火車
內部的對象,那麼整列火車包含的都是垃圾對象,能夠拋棄。最小數字火車
並不都是垃圾,那麼算法把它的注意力放到火車的最小數字車箱
上。在這個過程當中,算法將檢測到的被引用的對象轉移到其餘車箱,而後任何保留着車箱裏的對象都是可回收的。咱們知道有一種循環引用
的問題,而對於火車算法來講,保證整列火車中沒有循環的數據結構的關鍵是算法如何移動對象,包括下面幾個規則:
最小數字火車
的最小數字車箱
啦)中有一個對象存在來自火車站外的引用,這個對象就被轉移到正在被收集的火車以外的其餘車箱中去。所以,在每次執行時,火車算法或者收集最小數字火車的最小數字車箱,或者手機整列最小數字火車。而將對應移動到引用它們的火車中,相關的對象會變得集中。最後,稱爲垃圾的循環數據結構中的全部對象,無論有多大,會放置到同一列火車中去。而增大循環數據結構只會增大最終組成同一列火車的車箱數。前面已經說明,火車算法會先檢查最小數字火車是否徹底就是垃圾,而對於循環數據結構這種內部引用,它徹底能夠完成收集。
火車算法的目標是爲了給按代收集的垃圾收集器提供限定時間內的漸進式收集。
對於車箱來講,分配時能夠指定一個最大的內存size,而且每次執行只收集一個車箱,因此大部分狀況下,火車算法能夠保證每次的執行時間在某個最長時間限度內,不過不能確保每一次都是,由於算法執行的過程當中不只僅是拷貝對象。
爲了優化收集過程,火車算法使用了記憶集合
。一個記憶集合
是一個數據結構,它包含了對一節車箱或者一列火車的外部引用。算法爲火車站(成熟對象空間)內的每節車箱和每列火車都維護了一個記憶集合。因此一節特定車箱的記憶集合記錄了指向車箱內對象的全部引用。一個空的記憶集合顯示車箱或者火車中的對象已經再也不被車箱或者火車外的任何變量引用(被遺忘了)。被遺忘的就是不可觸及的,能夠被回收。
記憶集合是一種能夠幫助火車算法更有效地完成工做的技術。當回車算法發現一節車箱或者一列火車的記憶集合是空的時,它就知道車箱裏面全是垃圾,能夠釋放回收這部分佔用的內存。
而且在移動一個對象到另外一節車箱是,記憶集合中的信息有助於它高效的更新全部指向被移動對象的引用。
咱們能夠經過限制一個車箱的大小來控制每次字節拷貝的上限,可是當移動一個很受歡迎的對象(有不少外部鏈接)時,所須要的工做幾乎是不可能限制的,每次算法移動一個對象時,它必須遍歷對象的記憶集合,更新每個鏈接,以便於使鏈接指向新的地址。由於指向一個對象的鏈接數是沒法限定的,因此更新一個被移動對象的全部鏈接所須要的的時間也沒法限定。
也就是說,在特定條件下,火車算法仍然多是破壞性的。不過除了這種受歡迎的清下不太實用外,火車算法大部分狀況工做的很好。
再次強調下到Sun JDK 6的時候就已經完全不包含train GC了,不事後續的GC策略能和這個差異有多大呢?對吧
Java語言裏,一個對象能夠擁有終結方法:這個方法是垃圾收集器在釋放對象前必需要運行的。而這個可能存在的終結方法使得任何Java虛擬機的垃圾收集器要完成的工做更加複雜。
給一個類加上終結方法,只須要這樣:
public class FinalizerTest {
@Override
protected void finalize() throws Throwable {
//do something
super.finalize();
}
}
複製代碼
垃圾收集器必須檢查它所發現的再也不被引用的對象是否存在finalize()
方法。
由於,存在終結方法時,Java虛擬機的垃圾收集器必須每次在收集時執行一些額外的步驟:
爲了減小釋放內存的時間,在掃描到某些對象擁有終結方法和運行終結方法之間,垃圾收集器能夠有選擇地插入一個步驟:
從根節點開始不可觸及
&&從將要被終結的對象開始不可觸及
這些對象不可能在執行終結方法時復活,它們能夠被當即釋放。與
的關係若是一個帶有終結方法的對象再也不被引用,而且它的總結方法已經執行過了,垃圾收集器必須使用某種方法記住這一點,而不能再次執行這個對象的終結方法。
若是這個對應已經被本身的終結方法或者其餘對象的終結方法復活了,稍後再次再也不被引用,垃圾收集器必須像對待一個沒有終結方法的對象同樣對待它(也就是finalize()
只會執行一次的緣由)。
使用Java編程時請記住,是垃圾收集器運行對象的終結方法。由於沒法預測垃圾收集什麼時候觸發,因此咱們也沒法預測對象的終結方法什麼時候執行。
在版本1.2以前,在垃圾收集器看來,堆中的每個對象都有三種狀態:
finalize()
方法的對象,而是全部的對象都會通過可復活狀態。finalize()
方法(再次引用一個對象),任何處於可復活狀態的對象均可能再次復活finalize()
(若是聲明瞭的話)後,再把可復活對象的狀態或者轉化爲可觸及,或者轉化爲不可觸及。在版本1.2中,對可觸及狀態延伸擴充了三個新狀態:軟可觸及
、弱可觸及
、影子可觸及
。而原來的可觸及狀態變成了強可觸及
。(其實就是咱們編程用到的弱引用、強引用啥的吧)
任何從根節點開始的任何直接引用,好比一個局部變量,是強可觸及。同理,任何由強可觸及對象所引用到的對象也是強可觸及
Reference
)Java提供了java.lang.rf.Reference
類用來管理對象鏈接,包含SoftReference
、WeakReference
、PhantomReference
三個實現類,繼承圖以下:
SoftReference
:封裝對引用目標的軟引用WeakReference
:封裝對引用目標的弱引用PhantomReference
:封裝對引用目標的影子引用強引用和上述三種引用的區別是,強引用禁止引用目標被垃圾收集,而軟引用、弱引用、影子引用不由止。
當須要建立一個Reference
的對象時,簡單的把強引用傳遞到對應的Reference
實現類的構造方法中去就能夠。以SoftReference
爲例:
public class ReferenceTest{
public static void main(String[] args) {
Cow c = new Cow();
SoftReference<Cow> softReference = new SoftReference<Cow>(c);
c = null;
}
}
class Cow{}
複製代碼
咱們經過維護softReference
來維護關於Cow
實例對象的軟引用。引用示意圖以下:
SoftReference
對象封裝了一個Cow
對象的軟引用。SoftReference
對象被一個局部變量softReference
強引用,==和全部的局部變量同樣,對於與垃圾收集器來講這是一個根節點==(這部分存疑哈)。
一旦一個引用對象建立後,它將一直維持到它的引用目標的軟引用,直到它被程序或者垃圾收集器清除。要清楚一個引用對象,程序或者垃圾收集器只需調用Referece
對象的clear()
方法。
前面講到,引用對象的目的是爲了可以指向某些對象,使這些對象能夠隨時被垃圾收集器收集。換個說法就是,垃圾收集器能夠隨意改變不是強可觸及對象的可觸及狀態。
若是想監聽這種狀態的變化,咱們可使用java.lang.rf.ReferenceQueue<T>
類。怎麼用呢,咱們看下Reference
的構造方法:
public abstract class Reference<T> {
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
}
複製代碼
而對於Reference
的總體結構,以下圖:
==說實話,Reference的體系以前沒怎接觸,只是在Android
中簡單使用WeakReference
的get
方法,等結束本篇垃圾收集,單獨撩撥一下==
那咱們就能夠這樣寫:
class ReferenceTest {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Cow> referenceQueue = new ReferenceQueue<>();
Cow c = new Cow();
WeakReference<Cow> softReference = new WeakReference<Cow>(c, referenceQueue);
//把對cow的強引用置爲空
c = null;
softReference.clear();
System.out.println("clear Reference");
System.out.println("獲取軟引用下的Cow = " + softReference.get());
//加入隊列,這一步虛擬機會去作,可是由於時間上的問題,咱們手動觸發一下
softReference.enqueue();
Reference<? extends Cow> cow = referenceQueue.remove();
System.out.println("從釋放隊列中獲取Cow = " + cow);
}
}
class Cow{}
複製代碼
當垃圾收集器決定收集弱可觸及對象的時候,它會清除WeakReference
對象中引用的Cow
對象(經過clear()
方法)。而後可能當即把這個WeakReference
對象當即加入它的引用隊列中,也可能在稍後的某個時間加入。
爲了把引用對象加入它所關聯的隊列中,垃圾收集器會執行它的enqueue()
方法。只有在建立引用對象時關聯了一個隊列,而且當且僅當該對象第一次執行enqueue()
方法時,才把引用對象加入這個隊列中。
在不一樣的狀況下,垃圾收集器把軟引用、弱引用、影子引用對象加入隊列表示三種不一樣的可觸及性狀態的轉變。這一共表示了6中可觸及狀態,狀況以下:
請注意,垃圾收集器再把軟引用和弱引用對象加入關聯隊列時,是在他們的引用目標離開相應的可觸及狀態時(調用clear
)
而影子引用對象加入隊列是在引用目標進入相應狀態時(也就是構造一個影子引用對象,並執行enqueue()
後)。
也就是說垃圾收集器把軟引用或者弱引用對象加入隊列標誌着引用對象剛剛離開了軟可觸及或者弱可觸及狀態;而垃圾收集器把影子引用加入隊列標誌着引用目標已經進入了影子可觸及狀態。==影子可觸及對象會保持影子可觸及狀態,直到程序顯式地清除了引用對象==。
垃圾收集器對待軟、弱和影子對象的方法不一樣,是由於每一種都是被設計成爲程序提供不一樣的服務。
請注意,要使用軟引用或者弱引用的引用目標,能夠調用對象的get()
方法。若是引用目標沒有被清除,則返回被引用的對象;若是被清除了,則返回null
。
可是對於影子引用對象的get()
方法,始終返回null
;咱們看下PhantomReference
的源碼
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
複製代碼
真滴是簡潔啊。。。。==爲何要這樣呢?==
咱們前面描述了6個狀態,而對於影子可觸及狀態來講,它表示對象是不可復活的。若是影子引用的get()
方法有返回對象的話,那麼這個規則就要被打破了。
請記住==若是一個對象達到了影子可觸及狀態,它不能再復活。==
不過虛擬機設計的真的這麼嚴謹麼?
咱們看下面的代碼:
public static void main(String[] args) {
ReferenceQueue<Cow> referenceQueue = new ReferenceQueue<>();
Cow c = new Cow();
PhantomReference<Cow> softReference = new PhantomReference<Cow>(c, referenceQueue);
//把對cow的強引用置爲空
c = null;
System.out.println("get()獲取影子引用下的Cow = " + softReference.get());
//加入隊列,這一步虛擬機會去作,可是由於時間上的問題,咱們手動觸發一下
softReference.enqueue();
//remove 後就能夠取得影子引用對象了
Reference<? extends Cow> cow = referenceQueue.remove();
//上面的get()沒取到對象,那咱們反射試一下
Field field = Reference.class.getDeclaredField("referent");
field.setAccessible(true);
Object obj = field.get(cow);
System.out.println("從釋放隊列中獲取Cow = " + obj);
//手動釋放一下
cow.clear();
//再反射獲取一下
obj = field.get(cow);
System.out.println("從釋放隊列中獲取Cow = " + obj);
}
複製代碼
輸出以下:
獲取影子引用下的Cow = null
從釋放隊列中獲取Cow = hua.lee.jvm.Cow@60e53b93
從釋放隊列中獲取Cow = null
複製代碼
咱們看到,==影子可觸及狀態的對象也是能夠被拿出來的嘛==
另外,有一點須要注意的是==影子可觸及狀態的對象是不會被垃圾收集器給回收的,咱們須要像上面的示例同樣手動clear()
來釋放對象==
虛擬機的實現須要在拋出OOM
以前清除掉軟引用,但在其餘狀況下能夠自行選擇清理的時間或者是否清除。實現最好是隻在內存不足的狀況下才去清除軟引用,清除的時候先清除老的而不是新的,清除長期未用的而不是最近使用的。
軟引用可讓你在內存中緩存那些須要從外部費時獲取的數據,好比文件中、數據庫裏或者網絡上的數據。
只要虛擬機有足夠的內存,能夠在堆中保存全部的強引用數據和軟引用數據。
若是內存緊張,垃圾收集器會決定清除軟引用,回收被軟引用的數據所佔用的空間。下一次程序須要使用這個數據時,可能不得再也不次從外部數據源進行加載。
弱引用相似於軟引用,但不一樣的是:
弱引用的這種特性使得咱們能夠用關鍵字和值來建立規範映射。java.lang.WeakHashMap
類就是用弱引用提供這樣的規範映射。
能夠經過put()
方法加入鍵值對到WeakHashMap
的實例。不過和HashMap
不一樣的是,在WeakHashMap
中,關鍵字對象是經過一個關聯到引用隊列的弱引用實現的。若是垃圾收集器檢測到關鍵字對象時弱可觸及的,它會清除引用而且把弱引用到該對象的引用對象加入到各自的隊列。下次WeakHashMap
被訪問的時候,它會從引用隊列中拉出全部的被垃圾收集器存放的弱引用對象,並清除和其有關的映射關係。