Java引用概述
StrongReference(強(qiáng)引用) 不存在這個(gè)類(lèi) 默認(rèn)實(shí)現(xiàn)
Java.lang.ref提供了與 Java垃圾回收器密切相關(guān)的引用類(lèi)庞瘸。SoftReference(軟引用),WeakReference(弱引用)擎颖,PhantomReference(虛引用)。這四種引用的強(qiáng)度按照上面的順序依次減弱.
一椭懊、強(qiáng)引用(StrongReference)
-就是指在程序代碼中普遍存在的施流,類(lèi)似Object obj = new Object()這類(lèi)的引用,只要強(qiáng)引用還存在代咸,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。 我們一般都是使用強(qiáng)引用來(lái)對(duì)對(duì)象進(jìn)行引用成黄。如:
String tag = new String("T");
此處的 tag 引用就稱(chēng)之為強(qiáng)引用呐芥。而強(qiáng)引用有以下特征:
- 強(qiáng)引用可以直接訪(fǎng)問(wèn)目標(biāo)對(duì)象。
- 強(qiáng)引用所指向的對(duì)象在任何時(shí)候都不會(huì)被系統(tǒng)回收奋岁。
- 強(qiáng)引用可能導(dǎo)致內(nèi)存泄漏
只有顯式地設(shè)置o為null思瘟,或超出對(duì)象的生命周期范圍,則gc認(rèn)為該對(duì)象不存在引用闻伶,這時(shí)就可以回收這個(gè)對(duì)象滨攻。具體什么時(shí)候收集這要取決于gc的算法。
二、軟引用(SoftReference)
是用來(lái)描述一些還有用但并非必須的對(duì)象铡买。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象更鲁,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前霎箍,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收奇钞。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常漂坏。
對(duì)于軟引用關(guān)聯(lián)著的對(duì)象景埃,如果內(nèi)存充足,則垃圾回收器不會(huì)回收該對(duì)象顶别,如果內(nèi)存不夠了谷徙,就會(huì)回收這些對(duì)象的內(nèi)存。在 JDK 1.2 之后驯绎,提供了 SoftReference 類(lèi)來(lái)實(shí)現(xiàn)軟引用完慧。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用剩失,如果軟引用所引用的對(duì)象被垃圾回收器回收屈尼,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
demo
public class SoftRefTest {
private static ReferenceQueue<MyObject> softQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) softQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
// -Xmx5M -XX:+PrintGCDetails
public static void main(String[] args) {
MyObject object = new MyObject();
SoftReference<MyObject> softRef = new SoftReference<>(object, softQueue);
new Thread(new CheckRefQueue()).start();
object = null; //刪除強(qiáng)引用
System.gc();
System.out.println("After GC: Soft Get= " + softRef.get());
System.out.println("分配大塊內(nèi)存");
byte[] b = new byte[5 * 1024 * 928];
System.out.println("After new byte[]:Soft Get= " + softRef.get());
System.gc();
}
}
out:
[GC (Allocation Failure) [PSYoungGen: 1024K->481K(1536K)] 1024K->513K(5632K), 0.0038919 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (System.gc()) [PSYoungGen: 682K->481K(1536K)] 714K->537K(5632K), 0.0035611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 481K->0K(1536K)] [ParOldGen: 56K->498K(4096K)] 537K->498K(5632K), [Metaspace: 2717K->2717K(1056768K)], 0.0068592 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
After GC: Soft Get= I am MyObject
分配大塊內(nèi)存
[GC (Allocation Failure) [PSYoungGen: 40K->32K(1536K)] 539K->530K(5632K), 0.0006437 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32K->64K(1536K)] 530K->562K(5632K), 0.0004163 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(1536K)] [ParOldGen: 498K->488K(4096K)] 562K->488K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0070447 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 488K->488K(5632K), 0.0006157 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[PSYoungGen: 0K->0K(1536K)] [ParOldGen: 488K->476K(4096K)] 488K->476K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0057286 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
MyObject's finalize called
Object for SoftReference is null
Heap
PSYoungGen total 1536K, used 61K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe0f740,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 476K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 11% used [0x00000007bfa00000,0x00000007bfa77338,0x00000007bfe00000)
Metaspace used 2749K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 294K, capacity 386K, committed 512K, reserved 1048576K
at com.gxgeek.javabasic.ref.SoftRefTest.main(SoftRefTest.java:55)
構(gòu)造MyObject對(duì)象拴孤,并將其賦值給object變量脾歧,構(gòu)成強(qiáng)引用。然后使用SoftReference構(gòu)造這個(gè)MyObject對(duì)象的軟引用softRef演熟,并注冊(cè)到softQueue引用隊(duì)列鞭执。當(dāng)softRef被回收時(shí),會(huì)被加入softQueue隊(duì)列芒粹。設(shè)置obj=null兄纺,刪除這個(gè)強(qiáng)引用,因此化漆,系統(tǒng)內(nèi)對(duì)MyObject對(duì)象的引用只剩下軟引用估脆。此時(shí),顯示調(diào)用GC获三,通過(guò)軟引用的get()方法旁蔼,取得MyObject對(duì)象的引用,發(fā)現(xiàn)對(duì)象并未被回收疙教,這說(shuō)明GC在內(nèi)存充足的情況下棺聊,不會(huì)回收軟引用對(duì)象。
接著贞谓,請(qǐng)求一塊大的堆空間限佩,這個(gè)操作會(huì)使系統(tǒng)堆內(nèi)存使用緊張,從而產(chǎn)生新一輪的GC。在這次GC后祟同,softRef.get()不再返回MyObject對(duì)象作喘,而是返回null,說(shuō)明在系統(tǒng)內(nèi)存緊張的情況下晕城,軟引用被回收泞坦。軟引用被回收時(shí),會(huì)被加入注冊(cè)的引用隊(duì)列砖顷。
二贰锁、弱引用(SoftReference)
WeakReference 是弱于 SoftReference 的引用類(lèi)型。弱引用的特性和基本與軟引用相似滤蝠,區(qū)別就在于弱引用所指向的對(duì)象只要進(jìn)行系統(tǒng)垃圾回收豌熄,不管內(nèi)存使用情況如何,永遠(yuǎn)對(duì)其進(jìn)行回收(get() 方法返回 null)物咳。
弱引用有以下特征:
- 弱引用使用 get()方法取得對(duì)象的強(qiáng)引用從而訪(fǎng)問(wèn)目標(biāo)對(duì)象锣险。
- 一旦系統(tǒng)內(nèi)存回收,無(wú)論內(nèi)存是否緊張览闰,弱引用指向的對(duì)象都會(huì)被回收芯肤。
- 弱引用也可以避免 Heap 內(nèi)存不足所導(dǎo)致的異常。
public class WeakRefTest {
private static ReferenceQueue<MyObject> weakQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) weakQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("刪除的弱引用為:" + obj + " but獲取弱引用的對(duì)象obj.get()=" + obj.get());
}
}
}
public static void main(String[] args) {
MyObject object = new MyObject();
Reference<MyObject> weakRef = new WeakReference<>(object, weakQueue);
System.out.println("創(chuàng)建的弱引用為:" + weakRef);
new Thread(new CheckRefQueue()).start();
object = null;
System.out.println("Before GC: Weak Get= " + weakRef.get());
System.gc();
System.out.println("After GC: Weak Get= " + weakRef.get());
}
}
out:
創(chuàng)建的弱引用為:java.lang.ref.WeakReference@2503dbd3
Before GC: Weak Get= I am MyObject
After GC: Weak Get= null
MyObject's finalize called
刪除的弱引用為:java.lang.ref.WeakReference@2503dbd3 but獲取弱引用的對(duì)象obj.get()=null
可以看到焕济,在GC之前纷妆,弱引用對(duì)象并未被垃圾回收器發(fā)現(xiàn),因此通過(guò) weakRef.get()可以獲取對(duì)應(yīng)的對(duì)象引用晴弃。但是只要進(jìn)行垃圾回收掩幢,弱引用一旦被發(fā)現(xiàn),便會(huì)立即被回收上鞠,并加入注冊(cè)引用隊(duì)列中际邻。此時(shí)再試圖通過(guò)weakRef.get()獲取對(duì)象的引用就會(huì)失敗。
Java 虛引用
public class PhantomRefTest {
private static ReferenceQueue<MyObject> phanQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) phanQueue.remove();
System.out.println("刪除的虛引用為:" + obj + " but獲取虛引用的對(duì)象obj.get()=" + obj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
Reference<MyObject> phanRef = new PhantomReference<>(object, phanQueue);
System.out.println("創(chuàng)建的虛引用為:" + phanRef);
new Thread(new CheckRefQueue()).start();
object = null;
TimeUnit.SECONDS.sleep(1);
int i = 1;
while (true) {
System.out.println("第" + i++ + "次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
out:
創(chuàng)建的虛引用為:java.lang.ref.PhantomReference@2503dbd3
第1次gc
MyObject's finalize called
第2次gc
刪除的虛引用為:java.lang.ref.PhantomReference@2503dbd3 but獲取虛引用的對(duì)象obj.get()=null
PhantomReference是所有“弱引用”中最弱的引用類(lèi)型芍阎。不同于軟引用和弱引用世曾,虛引用無(wú)法通過(guò) get() 方法來(lái)取得目標(biāo)對(duì)象的強(qiáng)引用從而使用目標(biāo)對(duì)象,觀(guān)察源碼可以發(fā)現(xiàn) get() 被重寫(xiě)為永遠(yuǎn)返回 null谴咸。
那虛引用到底有什么作用轮听?其實(shí)虛引用主要被用來(lái) 跟蹤對(duì)象被垃圾回收的狀態(tài),通過(guò)查看引用隊(duì)列中是否包含對(duì)象所對(duì)應(yīng)的虛引用來(lái)判斷它是否 即將被垃圾回收岭佳,從而采取行動(dòng)血巍。它并不被期待用來(lái)取得目標(biāo)對(duì)象的引用,而目標(biāo)對(duì)象被回收前珊随,它的引用會(huì)被放入一個(gè) ReferenceQueue 對(duì)象中述寡,從而達(dá)到跟蹤對(duì)象垃圾回收的作用柿隙。
引用類(lèi)型 | 取得目標(biāo)對(duì)象方式 | 垃圾回收條件 | 是否可能內(nèi)存泄漏 |
---|---|---|---|
強(qiáng)引用 | 直接調(diào)用 | 不回收 | 可能 |
軟引用 | 通過(guò) get() 方法 | 視內(nèi)存情況回收 | 不可能 |
弱引用 | 通過(guò) get() 方法 | 永遠(yuǎn)回收 | 不可能 |
虛引用 | 無(wú)法取得 | 不回收 | 可能 |
FinalReference 以及 Finzlizer
st=>start: Reference
e=>end: Finalizer
op=>operation: FinalReference
st->op->e
FinalReference 作為 java.lang.ref 里的一個(gè)不能被公開(kāi)訪(fǎng)問(wèn)的類(lèi),又起到了一個(gè)什么樣的作用呢鲫凶?作為他的子類(lèi)禀崖, Finalizer 又在垃圾回收機(jī)制里扮演了怎么樣的角色呢?
實(shí)際上螟炫,F(xiàn)inalReference 代表的正是 Java 中的強(qiáng)引用波附,如這樣的代碼 :
Bean bean = new Bean();
在虛擬機(jī)的實(shí)現(xiàn)過(guò)程中,實(shí)際采用了 FinalReference 類(lèi)對(duì)其進(jìn)行引用不恭。而 Finalizer叶雹,除了作為一個(gè)實(shí)現(xiàn)類(lèi)外,更是在虛擬機(jī)中實(shí)現(xiàn)一個(gè) FinalizerThread换吧,以使虛擬機(jī)能夠在所有的強(qiáng)引用被解除后實(shí)現(xiàn)內(nèi)存清理。
讓我們來(lái)看看 Finalizer 是如何工作的钥星。首先沾瓦,通過(guò)聲明 FinalizerThread,并將該線(xiàn)程實(shí)例化谦炒,設(shè)置為守護(hù)線(xiàn)程后贯莺,加入系統(tǒng)線(xiàn)程中去。
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();
}
在 GC 的過(guò)程中宁改,當(dāng)一個(gè)強(qiáng)引用被釋放缕探,由系統(tǒng)垃圾收集器標(biāo)記后的對(duì)象,會(huì)被加入 Finalizer 對(duì)象中的 ReferenceQueue 中去还蹲,并調(diào)用 Finalizer.runFinalizer() 來(lái)執(zhí)行對(duì)象的 finalize 方法爹耗。
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
}
}
}
}
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
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();
}
注意,標(biāo)記處所調(diào)用的 invokeFinalizeMethod 為 native 方法谜喊,由于 finalize 方法在 Object 類(lèi)中被聲明為 protected潭兽,這里必須采用 native 方法才能調(diào)用。隨后通過(guò)將本地強(qiáng)引用設(shè)置為空斗遏,以便使垃圾回收器清理內(nèi)存山卦。
可以看到,通過(guò)這樣的方法诵次,Java 將四種引用對(duì)象類(lèi)型:軟引用 (SoftReference)账蓉,弱引用 (WeakReference),強(qiáng)引用 (FinalReference)逾一,虛引用 (PhantomReference) 平等地對(duì)待铸本,并在垃圾回收器中進(jìn)行統(tǒng)一調(diào)度和管理。
何時(shí)注冊(cè)(實(shí)例化FinalReference)
JVM在類(lèi)加載的時(shí)候會(huì)遍歷當(dāng)前類(lèi)的所有方法嬉荆,包括父類(lèi)的方法归敬,只要有一個(gè)參數(shù)為空且返回void的非空f(shuō)inalize方法就認(rèn)為這個(gè)類(lèi)在創(chuàng)建對(duì)象的時(shí)候需要進(jìn)行注冊(cè)。
對(duì)象的創(chuàng)建其實(shí)是被拆分成多個(gè)步驟,注冊(cè)的時(shí)機(jī)可以在為對(duì)象分配好內(nèi)存空間后汪茧,也可以在構(gòu)造函數(shù)返回之前椅亚,這個(gè)點(diǎn)由-XX:-RegisterFinalizersAtInit控制,這個(gè)參數(shù)默認(rèn)為true舱污,即:在構(gòu)造函數(shù)返回之前調(diào)用呀舔。注冊(cè)入口是Finalizer的register()方法。
GC回收問(wèn)題
對(duì)象因?yàn)镕inalizer的引用而變成了一個(gè)臨時(shí)的強(qiáng)引用扩灯,即使沒(méi)有其他的強(qiáng)引用媚赖,還是無(wú)法立即被回收;
對(duì)象至少經(jīng)歷兩次GC才能被回收珠插,因?yàn)橹挥性贔inalizerThread執(zhí)行完了f對(duì)象的finalize方法的情況下才有可能被下次GC回收惧磺,而有可能期間已經(jīng)經(jīng)歷過(guò)多次GC了,但是一直還沒(méi)執(zhí)行對(duì)象的finalize方法捻撑;
CPU資源比較稀缺的情況下FinalizerThread線(xiàn)程有可能因?yàn)閮?yōu)先級(jí)比較低而延遲執(zhí)行對(duì)象的finalize方法磨隘;
因?yàn)閷?duì)象的finalize方法遲遲沒(méi)有執(zhí)行,有可能會(huì)導(dǎo)致大部分f對(duì)象進(jìn)入到old分代顾患,此時(shí)容易引發(fā)old分代的GC番捂,甚至Full GC,GC暫停時(shí)間明顯變長(zhǎng)江解,甚至導(dǎo)致OOM设预;
對(duì)象的finalize方法被調(diào)用后,這個(gè)對(duì)象其實(shí)還并沒(méi)有被回收犁河,雖然可能在不久的將來(lái)會(huì)被回收鳖枕。
finalizer的生存周期
- 在創(chuàng)建對(duì)象時(shí),如果對(duì)象override了finalize()方法呼股,jvm會(huì)同時(shí)創(chuàng)建一個(gè)Finalizer對(duì)象
- 所有Finalizer對(duì)象組成了一個(gè)雙向鏈表
- 所有Finalizer對(duì)象都有一個(gè)名為queue的成員變量耕魄,指向的都是Finalizer類(lèi)的靜態(tài)Queue。
- cms gc執(zhí)行到mark階段的最后時(shí)彭谁,會(huì)把需要gc的對(duì)象加入到Reference的pending list中吸奴。
- 有一個(gè)專(zhuān)門(mén)的高級(jí)別線(xiàn)程Reference Handler處理pending list,把pending list中的對(duì)象取出來(lái)缠局,放到這個(gè)對(duì)象所指的Reference Queue中则奥,對(duì)于Finalizer對(duì)象來(lái)說(shuō),這個(gè)queue指向Finalizer類(lèi)的靜態(tài)Queue狭园。
- Finalizer類(lèi)有一個(gè)專(zhuān)門(mén)的線(xiàn)程負(fù)責(zé)從queue中取對(duì)象读处,并且執(zhí)行finalizer引用的對(duì)象的finalize函數(shù)。