你們應該都知道 Java 中除了強引用類型外還有幾個特殊的引用類型:軟引用(SoftReference)、弱引用(WeakReference)以及虛引用(PhantomReference),這幾個特殊的引用類型在 java.lang.ref
下也有對應的類。你們也應該都知道引入這幾個特殊的引用類型是和 Java 的對象回收息息相關的,今天這篇文章就是來分析這些引用類型對 Java 中的對象回收有什麼影響。html
分析引用類型以前咱們必需要明確的一點是:JVM 並非採用引用計數的方式來決定對象是否能夠被回收,而是經過分析對象的可達性來決定是否回收的。在不考慮特殊引用類型的狀況下,對象的可達性能夠被簡單地分爲兩類:可達對象和不可達對象,被判斷爲不可達的對象在垃圾回收階段就會被回收器回收。java
JVM 是如何分析對象的可達性的呢?首先,在分析可達性以前 JVM 會找出那些對虛擬機運行相當重要的對象,沒有這些對象整個應用就沒法正常運行下去,這些對象被稱之爲根對象(GC Roots);而後從根對象開始分析全部能被引用到的對象,這些對象即是可達的,而剩下的天然都是不可達對象。api
JVM 尋找根對象的過程被稱爲根節點枚舉,尋找的根對象有:緩存
虛擬機棧中引用的對象;bash
類靜態變量引用的對象;oracle
類常量引用的對象;app
JNI 的 native 方法棧中引用的對象;less
JNI 中的 global 對象;eclipse
...jsp
憑什麼這些對象是根對象,別的對象就不是呢?王侯將相寧有種乎!不是,並無任何欽定的意思,這都是有緣由的。虛擬機棧,也就是每一個線程運行時的方法棧,棧中的每一個棧幀對應一個方法調用,棧幀中的局部變量表裏保存着對應方法的局部變量。試想一下,若是這些正在執行的方法中局部變量引用着的對象被回收了,這個線程還能正常運行嗎?native 方法棧也是同理。另外,方法運行時,隨時均可能會訪問到類中的靜態變量以及常量,這些類確定也是不能被回收的,JNI global 對象也是同理。固然還有一些沒有列舉到的根對象類型,能夠參考 Eclipse 堆內存分析裏列舉出來的各類根對象類型。
前面只是將對象的可達性簡單地劃分爲可達和不可達,那是在沒有考慮幾種特殊引用類型的狀況下,若是把這些特殊引用類型考慮在內的話,對象的可達性多達五種:強可達(Strongly Reachable)、軟可達(Softly Reachable)、弱可達(Weakly Reachable)、虛可達(Phantom Reachable)和不可達(Unreachable)。
咱們都知道如何去構造不一樣的引用類型,以下代碼:
public class Foo {
public void bar() {
// far變量經過強引用形式引用一個新建立的Far對象
far = new Far();
// 經過SoftReference軟引用一個Far對象
farSoftReference = new SoftReference<Far>(far);
farSoftReference = new SoftReference<Far>(far, softReferenceQueue);
// 經過WeakReference弱引用一個Far對象
farWeakReference = new WeakReference<Far>(far);
farWeakReference = new WeakReference<Far>(far, weakReferenceQueue);
// 經過PhantomReference虛引用一個Far對象
farPhantomReference = new PhantomReference<Far>(far, phantomReferenceQueue);
}
}
複製代碼
強引用是 Java 中的默認引用類型,當建立了一個對象並賦值給某個變量(局部變量或成員變量等)時,就創建了一個從該變量到指定對象的一個強引用。特殊引用類型不一樣於強引用類型,例如當經過 farSoftReference = new SoftReference<Far>(far)
建立了一個 SoftReference
對象時,一方面是創建了一個從 farSoftReference
變量到這個 SoftReference
對象的強引用,另外一方面也創建了一個從 farSoftReference
變量到 Far
對象的軟引用。在下面的介紹中,當我說對象 A 軟引用對象 B 時,個人意思是對象 A 中包含了一個 SoftReference
類型的成員變量,這個成員變量軟引用着對象 B,好比下面的代碼示例中,我就會說(某個)Tar 對象 軟引用着(某個)Foo 對象。
public class Tar {
private SoftReference<Foo> fooSoftRef;
public Tar(Foo foo) {
fooSoftRef = new SoftReference<>(foo);
}
...
}
複製代碼
接下來我會詳細地介紹引用類型和對象可達性的關係以及對象的可達性如何影響對象的回收。
從前面的對象可達性分析介紹中不可貴知,一個可達對象可能有多個路徑能夠回溯到根對象,甚至不一樣的路徑能夠回溯到不一樣的根對象,那麼從根對象到可達對象的引用路徑就不是惟一的。強可達對象是存在至少一條從根對象到該對象的引用路徑,在這條引用路徑上全部的引用都是強引用類型。
上圖中,對象 C 雖然被對象 B 軟引用着,可是由於存在一條 R->A->C 的全是強引用類型的路徑,因此對象 C 是強可達的。
JVM 保證,強可達對象永遠不會被回收!
軟可達對象,首先對象不是強可達的,即不存在從根對象到該對象上全是強引用類型的引用路徑,可是至少存在一條從根對象到該對象的引用路徑,路徑中除了強引用類型外只有軟引用類型,這樣的對象即是軟可達的。
上圖中,對象 A、對象 C 以及對象 D 都是軟可達的,雖然對象 C 是被對象 A 強引用着,可是對象 A 自己是軟可達的,因此對象 C 也是軟可達的。因此你看,一個對象即便被強引用着,也多是軟可達的,好比這裏的對象 C;一個對象即便被軟引用着,也多是強可達的,好比強可達那個圖裏的對象 C。
JVM 是如何對待軟可達對象的呢?JVM 會盡可能保證在堆內存足夠的狀況下,不去回收軟可達對象,可是這種保證是不可靠的保證,由於很難界定堆內存是否足夠,還有當當前堆內存不足時是優先回收軟可達對象呢,仍是優先進行堆內存的擴容呢等等。可是 JVM 有一點能夠保證的是,在 JVM 堆內存達到上限即將拋出 OOM 異常以前會回收全部尚未被回收的軟可達對象,若是清理出來的空間依然沒法知足內存申請的需求則會真正拋出 OOM 異常。
其實在 openJDK 的虛擬機源碼裏能夠看到,針對軟可達對象 HotSpot VM 是有四種回收策略能夠選用的:
/** * src/share/vm/memory/referencePolicy.hpp */
class NeverClearPolicy : public ReferencePolicy {
public:
virtual bool should_clear_reference(oop p, jlong timestamp_clock) {
return false;
}
};
class AlwaysClearPolicy : public ReferencePolicy {
public:
virtual bool should_clear_reference(oop p, jlong timestamp_clock) {
return true;
}
};
class LRUCurrentHeapPolicy : public ReferencePolicy {
private:
jlong _max_interval;
public:
LRUCurrentHeapPolicy();
// Capture state (of-the-VM) information needed to evaluate the policy wfwf
void setup();
virtual bool should_clear_reference(oop p, jlong timestamp_clock);
};
class LRUMaxHeapPolicy : public ReferencePolicy {
private:
jlong _max_interval;
public:
LRUMaxHeapPolicy();
// Capture state (of-the-VM) information needed to evaluate the policy
void setup();
virtual bool should_clear_reference(oop p, jlong timestamp_clock);
};
複製代碼
NeverClearPolicy
就是一直不回收軟可達對象除非即將發生 OOM;AlwaysClearPolicy
就是每次 GC 都會回收全部軟可達對象;LRUCurrentHeapPolicy
是將當前堆的空閒內存(當前堆容量 - 當前已使用堆內存,單位MB)和 SoftRefLRUPolicyMSPerMB
的乘積作爲 SoftReference
對象存活的臨界值,GC 時,若是軟可達對象對應的引用 SoftReference
對象的存活時間超過了這個臨界值,那麼這個軟可達對象就會被回收,不然不回收;LRUMaxHeapPolicy
是將(堆最大容量 - 當前已使用堆內存,單位MB)和 SoftRefLRUPolicyMSPerMB
的乘積作爲 SoftReference
對象存活的臨界值,GC 時,若是軟可達對象對應的引用 SoftReference
對象的存活時間超過了這個臨界值,那麼這個軟可達對象就會被回收,不然不回收。
SoftRefLRUPolicyMSPerMB
是每 MB 內存對應的時間係數,單位毫秒(MS),HotSpot VM 能夠經過參數 -XX:SoftRefLRUPolicyMSPerMB=XXX
指定。當 HotSpot 處於 Client VM 時默認使用 LRUCurrentHeapPolicy
,處於 Server VM 時默認使用 LRUMaxHeapPolicy
。
Android ART 虛擬機中沒有那麼多回收策略,回收器會在每次 GC 時都會經過一個包含 GC 迭代相關信息的類 Iteration
的 GetClearSoftReference()
方法來判斷是否回收軟可達對象,若是回收就是所有回收,不回收就全都不回收。ART 中有好幾個回收器(Mark-Sweep、Concurrent-Copying、Semi-Space等等),各類回收器對待軟可達對象可能不太同樣,好比 Concurrent-Copying 回收器 和 sticky 的 Mark-Sweep 回收器就徹底不回收軟可達對象,非 sticky 的 Mark-Sweep 回收器以及 non-generational 的 Semi-Space 回收器每次都會回收,generational 的 Semi-Space 回收器只在 full GC 的時候回收。
從這些回收策略能夠看出軟可達對象的回收時機是有很大的不肯定性的。之前的不少文章介紹 SoftReference
適合用來緩存對象,我我的以爲選擇 SoftReference
來緩存對象須要慎重,由於軟可達對象回收時機的不肯定可會導能致堆中存留過多的軟可達對象,進而致使 GC 頻率的增長,從而引發程序的卡頓。
弱可達對象,對象既不是強可達的也不是軟可達的,而且至少存在一條從根對象到該對象的引用路徑,路徑中的引用類型只能包含強引用、軟引用和弱引用類型,且必須包含至少一個弱引用類型。
上圖中對象 C 和對象 D 都是弱可達的,由於它們都不是強可達或軟可達的,而且引用路徑中除了弱引用類型外,只包含強引用類型或軟引用類型。
JVM 如何處理弱可達對象?弱可達對象會在當次 GC 中被回收器回收。可達性分析是在對象回收以前進行的,當肯定了對象的可達性以後,回收器纔會以此爲依據進行對象的回收。若是在可達性分析時肯定了對象是弱可達的話,緊接着的回收階段,也就是我剛纔說的當次 GC,就會將這個對象回收。
可是,有可能你也會發現,弱可達對象的存活時間超出了你的預期,在某次 GC 時你認爲已經變成弱可達的對象並無被回收,把堆內存 dump 出來分析也並無發現問題,對象的確是弱可達的,爲何會出現這樣的問題呢?有兩種可能:1、咱們知道 JVM 中堆是分代的,可能當時的 WeakReference 對象以及被引用的弱可達對象都在老年代,而 GC 發生在新生代,沒有分析到這些對象,天然也不會回收它;2、WeakReference 對象和被引用的對象(referent)在不一樣的分代裏,而 JVM 默認採用的是 ReferenceBasedDiscovery
策略來發現這些弱引用類型,在這個策略中當引用對象和被引用對象在不一樣的分代裏時,就不會去發現並分析這些引用對象,更不會回收被引用的對象。
關於特殊引用類型的發現策略能夠參考 openJDK 源碼裏的相關註釋:
/** * src/share/vm/memory/referenceProcessor.cpp */
// We mention two of several possible choices here:
// #0: if the reference object is not in the "originating generation"
// (or part of the heap being collected, indicated by our "span"
// we don't treat it specially (i.e. we scan it as we would
// a normal oop, treating its references as strong references).
// This means that references can't be discovered unless their
// referent is also in the same span. This is the simplest,
// most "local" and most conservative approach, albeit one
// that may cause weak references to be enqueued least promptly.
// We call this choice the "ReferenceBasedDiscovery" policy.
// #1: the reference object may be in any generation (span), but if
// the referent is in the generation (span) being currently collected
// then we can discover the reference object, provided
// the object has not already been discovered by
// a different concurrently running collector (as may be the
// case, for instance, if the reference object is in CMS and
// the referent in DefNewGeneration), and provided the processing
// of this reference object by the current collector will
// appear atomic to every other collector in the system.
// (Thus, for instance, a concurrent collector may not
// discover references in other generations even if the
// referent is in its own generation). This policy may,
// in certain cases, enqueue references somewhat sooner than
// might Policy #0 above, but at marginally increased cost
// and complexity in processing these references.
// We call this choice the "RefeferentBasedDiscovery" policy. wfwf
bool ReferenceProcessor::discover_reference(oop obj, ReferenceType rt) {
...
}
複製代碼
虛可達對象,對象既不是強可達的、也不是軟可達的、也不是弱可達的,可是對象的引用路徑中存在虛引用類型。
上圖中對象 C 和對象 D 都是虛可達的。
JVM 如何處理虛可達對象?先來看下 PhantomReference
的 類註釋:
/**
* Phantom reference objects, which are enqueued after the collector
* determines that their referents may otherwise be reclaimed. Phantom
* references are most often used for scheduling pre-mortem cleanup actions in
* a more flexible way than is possible with the Java finalization mechanism.
*
* ...
*
* <p> Unlike soft and weak references, phantom references are not
* automatically cleared by the garbage collector as they are enqueued. An
* object that is reachable via phantom references will remain so until all
* such references are cleared or themselves become unreachable.
*
* ...
*
*/
複製代碼
看第一段註釋可能會讓人以爲當對象變成虛可達時,它就已經被回收了,將對應的虛引用對象加入到引用隊列中用於通知對象已經被回收了,這樣能夠作一些相似 finalize()
的清理工做。可是實際狀況並不是如此,後一段註釋中說明了虛引用對象並不會主動清理它所引用的對象。軟引用對象/弱引用對象在被引用對象變成軟可達/弱可達而且回收器決定回收這個被引用對象時會主動斷開引用對象和被引用對象之間的鏈接,其實也就是主動將 Reference
中的成員變量 referent
置爲 null
,這樣回收器才能真正的回收被引用對象。可是虛引用對象並不會這樣,它不會主動將成員變量 referent
置爲 null
,此時被引用對象會一直保留在內存中不會被回收,直到咱們手動調用 PhantomReference.clear()
方法去手動清理。
因此當對象變成虛可達時,回收器並不會回收該對象,可是會將 PhantomReference
對象加入到關聯的引用隊列中來通知用戶(開發者)被引用對象到達了虛可達狀態,這時能夠作一些善後清理工做,而後直到用戶(開發者)調用了 PhantomReference.clear()
後被引用對象纔會在後續的 GC 階段被回收。
不可達對象應該就不用細說了,一言以概之,不存在任何一條路徑可使不可達對象回溯到根對象。
不可達對象會在可達性分析完成後緊接着的 GC 階段被當即回收。
上述對象的五種可達性是按照由強到弱的順序來介紹的,對象的可達狀態是會變化的,但只會從較強的可達狀態向較弱的可達狀態變化而不會反過來(若是考慮對象須要執行 finalize()
方法的話,這是存在例外的)。並且上述對象可達性概念並非我本身編造的,你能夠在 java.lang.ref
的包註釋文檔 的 Reachability
一節中看到這些可達性的定義。
finalize()
方法對對象回收的影響咱們已經介紹了幾個特殊引用類型引伸出來的幾種對象可達性,以及對這幾種可達對象的回收處理,可是咱們沒有考慮到 finalize()
方法對對象回收的影響。若是某個對象的類覆寫(override)了 finalize()
方法,那麼這個對象在軟可達(且虛擬機決定回收這個軟可達對象)時、弱可達時、甚至「不可達」時,均可能不會被回收。 如今咱們就來看一下 finalize()
方法是如何影響一個對象的回收處理的。
若是類覆寫了 finalize()
方法,在建立該類的對象時,虛擬機同時也會建立一個 FinalReference
類型的對象引用着這個對象。FinalReference
繼承自 Reference
類,也是一個特殊引用類型。實際上虛擬機建立的是 FinalReference
的子類對象,也就是 Finalizer
類型的對象,源碼以下:
/*
* 虛擬機建立 Java 對象
* src/share/vm/oops/instanceKlass.cpp
*/
instanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
int size = size_helper(); // Query before forming handle.
KlassHandle h_k(THREAD, this);
instanceOop i;
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
// 看這裏!調用了 register_finalizer()
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
/*
* 虛擬機調用 Finalizer.register(Object) 方法的地方
* 這裏的參數 i 就是須要執行 finalize() 方法的對象
* src/share/vm/oops/instanceKlass.cpp
*/
instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
if (TraceFinalizerRegistration) {
tty->print("Registered ");
i->print_value_on(tty);
tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
}
instanceHandle h_i(THREAD, i);
// Pass the handle as argument, JavaCalls::call expects oop as jobjects
JavaValue result(T_VOID);
JavaCallArguments args(h_i);
methodHandle mh (THREAD, Universe::finalizer_register_method());
JavaCalls::call(&result, mh, &args, CHECK_NULL);
return h_i();
}
複製代碼
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static Finalizer unfinalized = null;
private Finalizer
next = null,
prev = null;
/* Invoked by VM */
static void register(Object finalizee) {
// 會在構造方法裏調用 add() 方法
new Finalizer(finalizee);
}
private Finalizer(Object finalizee) {
super(finalizee, queue);
add();
}
private void add() {
// 把本身添加到 unfinalized 表示的鏈表中
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
...
}
複製代碼
第一部分代碼是虛擬機 native 層的代碼,能夠看到在建立對象時,若是須要執行 finalize()
方法就會調用 Finalizer.register(Object)
方法。
第二部分代碼在 Finalizer.register(Object)
這個靜態方法中會建立一個 Finalizer
引用對象引用着那個須要執行 finalize()
方法的對象,而後會再調用 Finalizer.add()
這個實例方法,將 Finalizer
對象添加到靜態成員變量 unfinalized
表明的一個鏈表中。
由於有了 Finalizer
這個新的特殊引用類型,咱們須要定義一個新的可達狀態,當對象到達這個可達狀態時,對象的 finalize()
方法將處於等待執行狀態。咱們暫且將這種新的可達狀態叫「F-可達」,這是我本身隨便起的名字,由於 Finalizer
或者 FinalReference
都屬於 JRE 的內部實現細節,因此在官方文檔中並無定義對應可達狀態。
「F-可達」狀態對象的可達性要弱於軟可達對象和弱可達對象,可是要強於虛可達對象,因此對象能夠從軟可達狀態或者弱可達狀態變成「F-可達」狀態,這也是爲何官方文檔介紹弱可達對象時會有這麼一句:
When the weak references to a weakly-reachable object are cleared, the object becomes eligible for finalization.
在後續的文章中會深刻到虛擬機實現層面來分析對象的可達性,那時咱們就會知道爲何對象的可達性強弱順序是這樣的,如今先記住就行。
「F-可達」對象,對象既不是強可達的,也不是軟可達的,也不是弱可達的,可是對象須要執行 finalize()
方法而且該對象的 finalize()
方法尚未執行過。
當對象變成「F-可達」時,該對象對應的 Finalizer
對象會被添加到 Finalizer
類的靜態成員變量 private static ReferenceQueue<Object> queue
這個引用隊列中,由於從在 Finalizer.register(Object)
方法中建立 Finalizer
對象時關聯的引用隊列就是它。咱們來看 Finalizer
中是如何處理這個引用隊列的。
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
...
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
// 將這個 Finalizer 對象從 unfinalized 鏈表中移除
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
...
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
}
複製代碼
Finalizer
內部啓動了一個守護線程負責從引用隊列中取出 Finalizer
對象執行它的 runFinalizer()
方法。一個須要執行 finalize()
方法的對象有且只有一個對應的 Finalizer
對象,當這個 Finalizer
對象從引用隊列取出來時,它不再會出如今引用隊列中,因此它的 runFinalizer()
方法以致於對應對象的 finalize()
方法都只會執行一次。另外在 runFinalizer()
方法中當對象的 finalize()
方法執行完後, Finalizer
對象會調用自身的 clear()
方法來清除 Finalizer
對象到 finalizee 對象的引用,此時這個 finalizee 對象會從「F-可達」變成虛可達(若是存在虛引用的話)或不可達。
這是 finalize()
方法影響對象回收的一個方面,既它會阻止對象被回收直到 finalize()
方法被執行完畢。它影響對象回收的另外一方面在於這個對象的 finalize()
方法內部到底作了什麼。
若是你看過《深刻理解 Java 虛擬機:JVM 高級特性與最佳實踐》這本書,你應該知道對象在執行 finalize()
方法時可能會被從新引用到,進而阻礙對象的回收。
/** * @author 周志明 */
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, I am still alive :)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
//對象第一次成功拯救本身
SAVE_HOOK = null;
System.gc();
//由於finalize方法優先級很低,因此暫停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No, I am dead :(");
}
//下面這段代碼與上面的徹底相同,可是此次自救卻失敗了
SAVE_HOOK = null;
System.gc();
//由於finalize方法優先級很低,因此暫停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No, I am dead :(");
}
}
}
複製代碼
這段代碼裏,FinalizeEscapeGC
類的 finalize()
方法會將當前執行這個方法的對象賦值給一個靜態變量。咱們已經知道靜態變量屬於 GC Root,毫無疑問的強可達對象,因此執行完 finalize()
方法後,對象會從「F-可達」變成強可達對象,而不是變成虛可達或者不可達對象。
這是 finalize()
方法影響對象回收的另外一個方面,若是 finalize()
方法實現不當,頗有可能會形成內存泄漏,因此儘可能儘可能不要覆寫 finalize()
方法,咱們不必爲本身增添這種沒必要要的風險。固然這種拯救對象的方法只能使用一次,由於咱們已經知道對象的 finalize()
方法只會被執行一次。
finalize()
方法與虛可達「F-可達」對象的可達性要強於虛可達對象,因此當一個須要執行 finalize()
方法的對象到達虛可達對象時,說明它的 finalize()
方法必定已經執行過了,因此咱們可能須要對虛可達對象從新定義:虛可達對象,對象既不是強可達的、也不是軟可達的、也不是弱可達的、也不是「F-可達」的,可是對象的引用路徑中存在虛引用類型。
虛可達對象是最接近不可達對象的,也就是說它是最接近被回收的,可是它--相比於其餘非強可達的對象--也是最容易因使用不當而引發內存泄漏的。其餘可達狀態的對象,軟可達、弱可達、「F-可達」對象都會在虛擬機的控制下逐漸地往更弱的可達狀態前進。好比虛擬機會根據軟引用回收策略自動將 SoftReferent
對象和軟可達對象之間的引用清除,使軟可達對象變得弱可達或「F-可達」甚至不可達,對弱可達對象和「F-可達」對象虛擬機都會有這樣的自動清除引用關係的處理,但對虛可達對象沒有,只有在程序中主動調用 PhantomReference.clear()
方法纔會將它們的引用關係清除,不然虛可達對象會一直停留在內存中不會被回收。
由 Java 的幾個特殊引用類型:SoftReference
、WeakReference
、FinalReference
和PhantomReference
引出的幾種對象可達性以及它們是如何影響對象的回收暫時就介紹到這裏。不知道大家是否想過,爲何對象的可達性由強到弱是軟可達->弱可達->「F-可達」->虛可達,虛擬機內部究竟是如何處理這些特殊引用類型的,以及虛擬機究竟是如何實現可達性分析的。當我寫這篇文章的時候,這些問題不斷浮如今我腦海,爲了弄找到答案,爲了儘可能輸出正確、不誤導讀者的信息,我也在 JVM 的源碼裏進行了些許探索。後續有機會的話我會從虛擬機實現的角度來對對象的可達性分析以及引用類型的處理等進行介紹。
文章中引用的源碼都是 openJDK 項目 jdk8u 版本的,源碼連接以下: