- 為什么會有這4種引用
Java
中的引用的定義很傳統(tǒng):如果reference
類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個引用余掖。 這種定義很純粹寸爆,但是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài)盐欺,對于如何描述一些“食之無味赁豆,棄之可惜”的對象就顯得無能為力。 我們希望能描述這樣一類對象:當內(nèi)存空間還足夠時冗美,則能保留在內(nèi)存之中魔种;如果內(nèi)存空間在進行垃圾收集后還是非常緊張,則可以拋棄這些對象粉洼。 很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景节预。
說白了傳統(tǒng)的兩種應(yīng)用沒法描述對象生命周期中的多種狀態(tài),對象有哪些狀態(tài)呢属韧。
在
Java
中安拟,對象的生命周期包括以下幾個階段:
- 創(chuàng)建階段(
Created
) - 應(yīng)用階段(
In Use
) - 不可見階段(
Invisible
) - 不可達階段(
Unreachable
) - 收集階段(
Collected
) - 終結(jié)階段(
Finalized
) - 對象空間重分配階段(
De-allocated
)
創(chuàng)建階段(Created)
在創(chuàng)建階段系統(tǒng)通過下面的幾個步驟來完成對象的創(chuàng)建過程
為對象分配存儲空間
開始構(gòu)造對象
從超類到子類對static成員進行初始化
超類成員變量按順序初始化,遞歸調(diào)用超類的構(gòu)造方法
子類成員變量按順序初始化宵喂,子類構(gòu)造方法調(diào)用
一旦對象被創(chuàng)建糠赦,并被分派給某些變量賦值,這個對象的狀態(tài)就切換到了應(yīng)用階段
應(yīng)用階段(
In Use
)
對象至少被一個強引用持有著锅棕。
不可見階段(
Invisible
)
當一個對象處于不可見階段時拙泽,說明程序本身不再持有該對象的任何強引用,雖然該這些引用仍然是存在著的裸燎。
簡單說就是程序的執(zhí)行已經(jīng)超出了該對象的作用域了顾瞻。
不可達階段(
Unreachable
)
對象處于不可達階段是指該對象不再被任何強引用所持有。
與“不可見階段”相比德绿,“不可見階段”是指程序不再持有該對象的任何強引用荷荤,這種情況下,該對象仍可能被JVM
等系統(tǒng)下的某些已裝載的靜態(tài)變量或線程或JNI
等強引用持有著脆炎,這些特殊的強引用被稱為”GC root
”梅猿。存在著這些GC root
會導(dǎo)致對象的內(nèi)存泄露情況氓辣,無法被回收秒裕。
收集階段(
Collected
)
當垃圾回收器發(fā)現(xiàn)該對象已經(jīng)處于“不可達階段”并且垃圾回收器已經(jīng)對該對象的內(nèi)存空間重新分配做好準備時,則對象進入了“收集階段”钞啸。如果該對象已經(jīng)重寫了finalize()
方法几蜻,則會去執(zhí)行該方法的終端操作喇潘。
這里要特別說明一下:不要重載finazlie()方法!原因有兩點:
會影響JVM的對象分配與回收速度
在分配該對象時梭稚,JVM需要在垃圾回收器上注冊該對象颖低,以便在回收時能夠執(zhí)行該重載方法;在該方法的執(zhí)行時需要消耗CPU時間且在執(zhí)行完該方法后才會重新執(zhí)行回收操作弧烤,即至少需要垃圾回收器對該對象執(zhí)行兩次GC忱屑。
可能造成該對象的再次“復(fù)活”
在finalize()方法中,如果有其它的強引用再次持有該對象暇昂,則會導(dǎo)致對象的狀態(tài)由“收集階段”又重新變?yōu)椤皯?yīng)用階段”莺戒。這個已經(jīng)破壞了Java對象的生命周期進程,且“復(fù)活”的對象不利用后續(xù)的代碼管理急波。
終結(jié)階段
當對象執(zhí)行完finalize()方法后仍然處于不可達狀態(tài)時从铲,則該對象進入終結(jié)階段。在該階段是等待垃圾回收器對該對象空間進行回收澄暮。
對象空間重新分配階段
垃圾回收器對該對象的所占用的內(nèi)存空間進行回收或者再分配了名段,則該對象徹底消失了,稱之為“對象空間重新分配階段”泣懊。
- 哪4種伸辟,各有什么特點
- 強引用
強引用是使用最普遍的引用。如果一個對象具有強引用馍刮,那垃圾收器絕不會回收它自娩。當內(nèi)存空間不足,Java虛擬機寧愿拋出
OutOfM moryError
錯誤渠退,使程序異常終止忙迁,也不會靠隨意回收具有強引用 對象來解決內(nèi)存不足的問題。
- 軟引用
軟引用是用來描述一些還有用但并非必須的對象碎乃。對于軟引用關(guān)聯(lián)著的對象姊扔,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍進行第二次回收梅誓。如果這次回收還沒有足夠的內(nèi)存恰梢,才會拋出內(nèi)存溢出異常。
/**
* 軟引用何時被收集
* 運行參數(shù) -Xmx200m -XX:+PrintGC
* Created by ccr at 2018/7/14.
*/
public class SoftReferenceDemo {
public static void main(String[] args) throws InterruptedException {
//100M的緩存數(shù)據(jù)
byte[] cacheData = new byte[100 * 1024 * 1024];
//將緩存數(shù)據(jù)用軟引用持有
SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
//將緩存數(shù)據(jù)的強引用去除
cacheData = null;
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//進行一次GC后查看對象的回收情況
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//在分配一個120M的對象梗掰,看看緩存對象的回收情況
byte[] newData = new byte[120 * 1024 * 1024];
System.out.println("分配后" + cacheData);
System.out.println("分配后" + cacheRef.get());
}
}
第一次GC前null
第一次GC前[B@7d4991ad
[GC (System.gc()) 105728K->103248K(175104K), 0.0009623 secs]
[Full GC (System.gc()) 103248K->103139K(175104K), 0.0049909 secs]
第一次GC后null
第一次GC后[B@7d4991ad
[GC (Allocation Failure) 103805K->103171K(175104K), 0.0027889 secs]
[GC (Allocation Failure) 103171K->103171K(175104K), 0.0016018 secs]
[Full GC (Allocation Failure) 103171K->103136K(175104K), 0.0089988 secs]
[GC (Allocation Failure) 103136K->103136K(199680K), 0.0009408 secs]
[Full GC (Allocation Failure) 103136K->719K(128512K), 0.0082685 secs]
分配后null
分配后null
從上面的示例中就能看出嵌言,軟引用關(guān)聯(lián)的對象不會被
GC
回收。JVM
在分配空間時及穗,若果Heap
空間不足摧茴,就會進行相應(yīng)的GC
,但是這次GC
并不會收集軟引用關(guān)聯(lián)的對象埂陆,但是在JVM發(fā)現(xiàn)就算進行了一次回收后還是不足(Allocation Failure
)苛白,JVM
會嘗試第二次GC
娃豹,回收軟引用關(guān)聯(lián)的對象。
像這種如果內(nèi)存充足购裙,
GC
時就保留懂版,內(nèi)存不夠,GC
再來收集的功能很適合用在緩存的引用場景中躏率。在使用緩存時有一個原則躯畴,如果緩存中有就從緩存獲取,如果沒有就從數(shù)據(jù)庫中獲取薇芝,緩存的存在是為了加快計算速度私股,如果因為緩存導(dǎo)致了內(nèi)存不足進而整個程序崩潰,那就得不償失了恩掷。
- 弱引用
弱引用也是用來描述非必須對象的倡鲸,他的強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象黄娘,在垃圾回收時峭状,如果這個對象只被弱引用關(guān)聯(lián)(沒有任何強引用關(guān)聯(lián)他),那么這個對象就會被回收逼争。
/**
* 弱引用關(guān)聯(lián)對象何時被回收
* Created by ccr at 2018/7/14.
*/
public class WeakReferenceDemo {
public static void main(String[] args) throws InterruptedException {
//100M的緩存數(shù)據(jù)
byte[] cacheData = new byte[100 * 1024 * 1024];
//將緩存數(shù)據(jù)用軟引用持有
WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//進行一次GC后查看對象的回收情況
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//將緩存數(shù)據(jù)的強引用去除
cacheData = null;
System.gc();
//等待GC
Thread.sleep(500);
System.out.println("第二次GC后" + cacheData);
System.out.println("第二次GC后" + cacheRef.get());
}
}
第一次GC前[B@7d4991ad
第一次GC前[B@7d4991ad
第一次GC后[B@7d4991ad
第一次GC后[B@7d4991ad
第二次GC后null
第二次GC后null
從上面的代碼中可以看出优床,弱引用關(guān)聯(lián)的對象是否回收取決于這個對象有沒有其他強引用指向它。這個確實很難理解誓焦,既然弱引用關(guān)聯(lián)對象的存活周期和強引用差不多胆敞,那直接用強引用好了,干嘛費用弄出個弱引用呢杂伟?其實弱引用存在必然有他的應(yīng)用場景移层。
static Map<Object,Object> container = new HashMap<>();
public static void putToContainer(Object key,Object value){
container.put(key,value);
}
public static void main(String[] args) {
//某個類中有這樣一段代碼
Object key = new Object();
Object value = new Object();
putToContainer(key,value);
//..........
/**
* 若干調(diào)用層次后程序員發(fā)現(xiàn)這個key指向的對象沒有用了,
* 為了節(jié)省內(nèi)存打算把這個對象拋棄赫粥,然而下面這個方式真的能把對象回收掉嗎观话?
* 由于container對象中包含了這個對象的引用,所以這個對象不能按照程序員的意向進行回收.
* 并且由于在程序中的任何部分沒有再出現(xiàn)這個鍵,所以越平,這個鍵 / 值 對無法從映射中刪除频蛔。
* 很可能會造成內(nèi)存泄漏。
*/
key = null;
}
下面一段話摘自《Java核心技術(shù)卷1》:
設(shè)計
WeakHashMap
類是為了解決一個有趣的問題秦叛。如果有一個值晦溪,對應(yīng)的鍵已經(jīng)不再 使用了, 將會出現(xiàn)什么情況呢挣跋? 假定對某個鍵的最后一次引用已經(jīng)消亡三圆,不再有任何途徑引 用這個值的對象了。但是,由于在程序中的任何部分沒有再出現(xiàn)這個鍵嫌术,所以,這個鍵 / 值 對無法從映射中刪除牌借。為什么垃圾回收器不能夠刪除它呢度气? 難道刪除無用的對象不是垃圾回 收器的工作嗎?
遺憾的是膨报,事情沒有這樣簡單磷籍。垃圾回收器跟蹤活動的對象。只要映射對象是活動的现柠, 其中的所有桶也是活動的院领, 它們不能被回收。因此够吩,需要由程序負責(zé)從長期存活的映射表中 刪除那些無用的值比然。 或者使用
WeakHashMap
完成這件事情。當對鍵的唯一引用來自散列條
目時周循, 這一數(shù)據(jù)結(jié)構(gòu)將與垃圾回收器協(xié)同工作一起刪除鍵 / 值對强法。
下面是這種機制的內(nèi)部運行情況。
WeakHashMap
使用弱引用(weak references
) 保存鍵湾笛。WeakReference
對象將引用保存到另外一個對象中饮怯,在這里,就是散列鍵嚎研。對于這種類型的 對象蓖墅,垃圾回收器用一種特有的方式進行處理。通常临扮,如果垃圾回收器發(fā)現(xiàn)某個特定的對象 已經(jīng)沒有他人引用了论矾,就將其回收。然而杆勇, 如果某個對象只能由WeakReference
引用拇囊, 垃圾 回收器仍然回收它,但要將引用這個對象的弱引用放人隊列中靶橱。WeakHashMap
將周期性地檢 查隊列寥袭, 以便找出新添加的弱引用。一個弱引用進人隊列意味著這個鍵不再被他人使用关霸, 并 且已經(jīng)被收集起來传黄。于是,WeakHashMap
將刪除對應(yīng)的條目队寇。
除了WeakHashMap
使用了弱引用膘掰,ThreadLocal
類中也是用了弱引用。
- 虛引用
一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響识埋,也無法通過虛引用來獲取一個對象的實例凡伊。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。虛引用和弱引用對關(guān)聯(lián)對象的回收都不會產(chǎn)生影響窒舟,如果只有虛引用活著弱引用關(guān)聯(lián)著對象系忙,那么這個對象就會被回收。它們的不同之處在于弱引用的
get
方法惠豺,虛引用的get
方法始終返回null
,弱引用可以使用ReferenceQueue
,虛引用必須配合ReferenceQueue
使用银还。
jdk
中直接內(nèi)存的回收就用到虛引用,由于jvm
自動內(nèi)存管理的范圍是堆內(nèi)存洁墙,而直接內(nèi)存是在堆內(nèi)存之外(其實是內(nèi)存映射文件蛹疯,自行去理解虛擬內(nèi)存空間的相關(guān)概念),所以直接內(nèi)存的分配和回收都是有Unsafe
類去操作热监,java
在申請一塊直接內(nèi)存之后捺弦,會在堆內(nèi)存分配一個對象保存這個堆外內(nèi)存的引用,這個對象被垃圾收集器管理孝扛,一旦這個對象被回收羹呵,相應(yīng)的用戶線程會收到通知并對直接內(nèi)存進行清理工作。