主要內(nèi)容:
- 介紹
- 垃圾回收器
- 問題
- 強(qiáng)引用
- 軟引用
- 弱引用
- 虛引用
- 總結(jié)
介紹
在java中有以下四種引用:
- 強(qiáng)應(yīng)用(Strong References)
- 軟引用(Soft References)
- 弱引用(Weak References)
- 虛引用(Phantom References)
這些引用的區(qū)別主要在于垃圾回收器管理上。如果你從來沒有聽過這些引用,說明你僅僅使用過強(qiáng)引用。知道這些引用的區(qū)別,對你開發(fā)有幫助。尤其是當(dāng)你需要存儲臨時對象,并且不想使用一些緩存庫如:EHcache、Guava。
由于這些引用類型與JVM垃圾回收器關(guān)聯(lián)緊密,下面將簡單回顧下垃圾回收器知識。
垃圾回收器
Java和C++的主要區(qū)別在于內(nèi)存管理。在Java中,開發(fā)者不需要知道內(nèi)存是如何工作的(但是應(yīng)該要知道?。驗镴VM使用垃圾回收器對內(nèi)存進(jìn)行管理。
當(dāng)你創(chuàng)建一個對象時,對象被JVM分配到堆中。堆在內(nèi)存中是有大小限制的。因此,JVM需要經(jīng)常刪除對象來達(dá)到釋放內(nèi)存空間的目的。為了銷毀一個對象,JVM需要知道這個對象是否活躍。一個對象是否在使用,是通過GC root(garbage collection root)是否可達(dá)判斷的。例如:
如果一個對象C被對象B引用,對象B被對象A引用,對象A被一個GC root引用,因此C,B和A被認(rèn)為是活躍,被使用,可達(dá)的(如圖Case 1)。但是,如果B不再被B引用,這樣C和B就不再是活躍的,因此可以被回收(如圖Case 2)。
由于這篇文章不是主要介紹垃圾回收器的,所以不會深入介紹。主要有四種類型的GC roots:
- 局部變量(Local variables)
- 活躍線程(Active Java threads)
- 靜態(tài)變量(Static variables)
- JNI引用(JNI References):Java對象包含本地代碼,并且內(nèi)存不被JVM管理。
Oracle公司沒有指定JVM實現(xiàn)中如何管理內(nèi)存的規(guī)范,因此有多種實現(xiàn)。但是主要思想都是一樣的,如下:
- JVM使用一種recurent算法來找到不活躍對象并標(biāo)記
- 被標(biāo)記的對象被確定(finalized),通過調(diào)用finalize()方法,然后被回收。
- JVM有時會移動存活對象,主要為了留出一段連續(xù)的大內(nèi)存區(qū)域,減少內(nèi)存碎片。
問題
既然JVM負(fù)責(zé)管理內(nèi)存,為什么你還需要關(guān)心?因為你還需要擔(dān)心內(nèi)存泄露問題!
大部分情況下,你正在使用GC roots,而沒有察覺。例如:在你的程序中,需要存儲對象,因為初始化比較費資源。你可能會使用靜態(tài)集合類(List、Map等)去存儲,并且在程序的其他地方獲取這些對象。
public static Map<K, V> myStoredObjects= new HashMap<>();
但是,通過這種做法,會阻止垃圾回收器回收對象。如果使用不當(dāng),則會導(dǎo)致OutOfMemoryError。例如:
public class OOM { public static List<Integer> myCachedObjects = new ArrayList<>(); public static void main(String[] args) { for (int i = 0; i < 100_000_000; i++) { myCac(i); } } }
輸出如下:
Exception?in?thread?“main”?java.lang.OutOfMemoryError:?Java?heap?space
Java提供了不同的引用類型來避免OutOfMemoryError。
即使在程序仍然需要使用的時候,一些類型允許JVM釋放對象。這類問題都是開發(fā)者的職責(zé)。
強(qiáng)應(yīng)用
強(qiáng)引用是標(biāo)準(zhǔn)的引用。當(dāng)你像下面這樣新建一個對象時:
MyClass obj = new MyClass ();
你正在使用一個強(qiáng)引用"obj"指向MyClass的實例。當(dāng)垃圾回收器查找不活躍對象時,僅僅會判斷這些對象是否強(qiáng)可達(dá)(strongly reachable,即被GC root強(qiáng)引用連接)。
強(qiáng)引用可以強(qiáng)制JVM保留對象在堆中,直到對象不被使用為止。
軟引用
根據(jù)java API中軟引用描述:
“Soft?reference?objects,?which?are?cleared?at?the?discretion?of?the?garbage?collector?in?response?to?memory?demand”
上面描述指的是,軟引用的行為是可變的,不同的JVM(Oracle’s Hotspot, Oracle’s JRockit, IBM’s J9, …)中,你的程序可能執(zhí)行結(jié)果不同。
下面以O(shè)racle的JVM Hotspot(大多數(shù)使用的標(biāo)準(zhǔn)JVM)為例來看看是怎么管理軟引用的。下面是Oracle的文檔描述:
“The?default?value?is?1000?ms?per?megabyte,?which?means?that?a?soft?reference?will?survive?(after?the?last?strong?reference?to?the?object?has?been?collected)?for?1?second?for?each?megabyte?of?free?space?in?the?heap”
下面是具體的例子:假設(shè)堆的大小512Mb,空閑400Mb。
我們創(chuàng)建對象A,被對象cache軟引用和被B強(qiáng)應(yīng)用。因為A被B強(qiáng)引用,因此是強(qiáng)可達(dá),不會被垃圾回收器回收(上圖Case 1描述)
假設(shè)現(xiàn)在B被刪除,因此A僅僅被對象cache軟引用。如果A沒有被強(qiáng)引用,則下一個400s,A將在超時后被刪除回收掉(上圖Case 2描述)。
下面是一個軟引用的例子:
public class ExampleSoftRef { public static class A{ } public static class B{ private A strongRef; public void setStrongRef(A ref) { = ref; } } public static SoftReference<A> cache; public static void main(String[] args) throws InterruptedException{ //initialisation of the cache with a soft reference of instanceA Exam instanceA = new Exam(); cache = new SoftReference<Exam>(instanceA); instanceA=null; // instanceA is now only soft reachable and can be deleted by the garbage collector after some time T(5000); ... Exam instanceB = new Exam(); //since cache has a SoftReference of instance A, we can't be sure that instanceA still exists //we need to check and recreate an instanceA if needed instanceA=cac(); if (instanceA ==null){ instanceA = new Exam(); cache = new SoftReference<Exam>(instanceA); } in(instanceA); instanceA=null; // instanceA a is now only softly referenced by cache and strongly referenced by B so it cannot be cleared by the garbage collector ... } }
但是即使軟引用的對象被垃圾回收器自動回收,軟引用(soft references ,也是一種對象)卻不會被刪除!因此,你仍然需要清理掉它們。例如在一個只有64Mb大小的堆中,下面的代碼會在使用軟引用的時候,拋出OutOfMemoryException異常。
public class TestSoftReference1 { public static class MyBigObject{ //each instance has 128 bytes of data int[] data = new int[128]; } public static int CACHE_INITIAL_CAPACITY = 1_000_000; public static Set<SoftReference<MyBigObject>> cache = new HashSet<>(CACHE_INITIAL_CAPACITY); public static void main(String[] args) { for (int i = 0; i < 1_000_000; i++) { MyBigObject obj = new MyBigObject(); cac(new SoftReference<>(obj)); if (i%200_000 == 0){ Sy("size of cache:" + cac()); } } Sy("End"); } }
輸出如下:
size of cache:1 size of cache:200001 size of cache:400001 size of cache:600001 Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded
Oracle提供了ReferenceQueue來存放軟應(yīng)用。使用這個隊列,可以清理掉軟應(yīng)用,因此避免OutOfMemoryError問題。
使用軟引用隊列,同樣的代碼,在同樣的64Mb堆大小下,可以存儲更多的數(shù)據(jù)(5 million vs 1 million),如下測試:
public class TestSoftReference2 { public static int removedSoftRefs = 0; public static class MyBigObject { //each instance has 128 bytes of data int[] data = new int[128]; } public static int CACHE_INITIAL_CAPACITY = 1_000_000; public static Set<SoftReference<MyBigObject>> cache = new HashSet<>( CACHE_INITIAL_CAPACITY); public static ReferenceQueue<MyBigObject> unusedRefToDelete = new ReferenceQueue<>(); public static void main(String[] args) { for (int i = 0; i < 5_000_000; i++) { MyBigObject obj = new MyBigObject(); cac(new SoftReference<>(obj, unusedRefToDelete)); clearUselessReferences(); } Sy("End, removed soft references=" + removedSoftRefs); } public static void clearUselessReferences() { Reference<? extends MyBigObject> ref = unu(); while (ref != null) { if (ref)) { removedSoftRefs++; } ref = unu(); } } }
輸出如下:
End,?removed?soft?references=4976899
當(dāng)你想存儲更多數(shù)據(jù),并在JVM刪除對象的時候,可以重新實例化的情況下,使用軟應(yīng)用效果更佳。
弱引用
弱引用是一個比軟引用更不穩(wěn)定的類型。下面是Java API介紹:
“Suppose?that?the?garbage?collector?determines?at?a?certain?point?in?time?that?an?object?is?weakly?reachable.?At?that?time?it?will?atomically?clear?all?weak?references?to?that?object?and?all?weak?references?to?any?other?weakly-reachable?objects?from?which?that?object?is?reachable?through?a?chain?of?strong?and?soft?references.?At?the?same?time?it?will?declare?all?of?the?formerly?weakly-reachable?objects?to?be?finalizable.?At?the?same?time?or?at?some?later?time?it?will?enqueue?those?newly-cleared?weak?references?that?are?registered?with?reference?queues.”
上面的描述是指當(dāng)垃圾回收器檢查所有對象時,如果檢查到一個對象只有弱引用可達(dá)GC root(沒有強(qiáng)引用或軟引用可達(dá)),這個對象則會被標(biāo)記為回收,并別盡快刪除。弱引用的用法和軟引用類似。因此,參考軟引用時舉的例子。
Oracle提供了一個基于軟引用的非常有趣的例子即:WeakHashMap。這個Map的特點是key是軟引用。WeakHashMap可以當(dāng)做標(biāo)準(zhǔn)的Map使用,和Map的區(qū)別在于當(dāng)key被銷毀的時候,會自動清理( automatically clear itself),如下面例子:
public class ExampleWeakHashMap { public static Map<Integer,String> cache = new WeakHashMap<Integer, String>(); public static void main(String[] args) { Integer i5 = new Integer(5); cac(i5, "five"); i5=null; //the entry {5,"five"} will stay in the Map until the next garbage collector call Integer i2 = 2; //the entry {2,"two"} will stay in the Map until i2 is no more strongly referenced cac(i2, "two"); //remebmber the OutOfMemoryError at the chapter "problem", this time it won't happen // because the Map will clear its entries. for (int i = 6; i < 100_000_000; i++) { cac(i,S(i)); } } }
例如,我使用過WeakHashMap來解決下面的問題:存儲事務(wù)的多種信息。我使用這種結(jié)構(gòu):WeakHashMap<String,Map<K,V>> ,其中key的String包含了事務(wù)的id,Map<K,V>包含了事務(wù)生命周期中的信息。使用這種結(jié)構(gòu),我能通過事務(wù)id獲取到信息,當(dāng)事務(wù)銷毀的時候,key中會被銷毀,也不用清理對應(yīng)的Map<K,V>信息。
Oracle建議使用WeakHashMap作為規(guī)范的(canonicalized)Map。
虛引用
在垃圾回收過程中,沒有被GC root強(qiáng)/軟引用的對象會被刪除回收。在被刪除之前,方法finalize()會被調(diào)用。當(dāng)一個對象被finalized的時候,但是還未被刪除,此時變?yōu)椤皃hantom reachable”,即在GC root和對象之間只有虛引用。
和軟引用、弱引用不同的是,使用虛引用可以防止對象被刪除。開發(fā)者需要移除虛引用,對象才能被銷毀。為了清理虛引用,當(dāng)一個對象處于finalized的時候,需要使用ReferenceQueue來保存虛引用。
虛引用不能獲取引用的對象。通過get()方法,返回的始終都是null,因此開發(fā)者不能使虛引用對象重新回到強(qiáng)/軟/弱引用。這是合理的,當(dāng)對象不需要再工作時,比如覆蓋finalize() 方法實現(xiàn)清理資源。
因為引用對象不可達(dá),我沒有看到虛引用的用處。一個例子是,如果你需要在一個對象處于finalized時,需要指定finalize() 行為。
總結(jié)
我希望你現(xiàn)在對引用有一個很好的認(rèn)識。大部分情況下,你不用知道具體使用這些引用(你也不應(yīng)該)。但是,很多框架都使用了這些引用。如果你知道一個事物是如果工作的,你就能更好理解這些概念。
如果你更喜歡看視頻介紹,可以通過YouTube:。
個人總結(jié):
是否回收:強(qiáng)引用不會回收;軟應(yīng)用在不同的JVM中實現(xiàn)不同,如果在Oracle的HotSpot中,則會定時過期清理,也會導(dǎo)致OOM問題;弱引用,在GC的時候會回收,當(dāng)key設(shè)置null,則對象會自動回收,不會導(dǎo)致OOM;虛引用,會回收,被調(diào)用之前會調(diào)用finalize()方法。
使用場景:強(qiáng)應(yīng)用經(jīng)常使用;軟引用在堆大小有限,可以存更多對象場景,使用時,需要判斷對象是否存在,不存在則重新實例化,適合非必須大對象的緩存;若引用,WeakHashMap就使用了;虛引用,在NIO的堆外內(nèi)存管理中使用到(下一篇文章進(jìn)行分析)。
1.《【i2stay什么意思】專題java引用(翻譯)》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《【i2stay什么意思】專題java引用(翻譯)》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/yule/3333748.html