日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入理解java中的Soft references amp;amp; Weak references amp;amp; Phantom reference

發(fā)布時間:2024/1/17 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解java中的Soft references amp;amp; Weak references amp;amp; Phantom reference 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

引言

Ethan Nicholas 在他的一篇文章中說:他面試了20多個Java高級工程師,他們每個人都至少有5年的Java從業(yè)經(jīng)驗,當他問這些工程師對于Weak References 的理解時,只有其中的2個人知道Weak References 的存在,而這2個人中,只有1人知道如何去使用Weak References,而其他人甚至都不知道Java中有Weak References的存在。

大家可能會想,我接觸了Java這么多年,從來都沒有使用過Weak References啊,它真的是一個有用的技術(shù)嗎?對于它有用沒用,我相信大家看完這篇文章,理解了它以后,自有判斷。如果你從業(yè)了3年以上的Java開發(fā),你不知道如何使用Weak References也還可以原諒,有可能你的項目還沒有那么復(fù)雜。但是如果你甚至都沒見過它在哪使用的,我覺得你可能讀的源碼太少了。我相信大家都知道ThreadLocal?這個類吧,你可以去看一下它的靜態(tài)內(nèi)部類ThreadLocalMap, 如果你想見到更多它的應(yīng)用,我給大家推薦個網(wǎng)站:searchcode,這個網(wǎng)站會根據(jù)你輸入的代碼片段,來搜索幾大源碼托管平臺上使用你輸入代碼片段的工程,大家可以輸入WeakReference試一試。

并不說只有你成為一個Weak References方面的專家,你才是一個優(yōu)秀的Java開發(fā)者,但是,你至少要了解我們在什么樣的場景下需要使用它,That’s enough.

由于理解Weak References && soft references 會涉及到JVM的垃圾收集的一些知識,如果你對這方面沒有了解,請你參考我的這篇文章:Hotspot虛擬機- 垃圾收集算法和垃圾收集器

Java中的4種reference

在Java中,有4種reference類型,它們從強到弱,依次如下:

  • Strong reference :?大家平常寫代碼的引用都是這種類型的引用,它可以防止引用的對象被垃圾回收。
  • Soft reference :?它引用的對象只有在內(nèi)存不足時,才會被回收。
  • Weak reference :?它并不會延長對象的生命周期,即它不能阻止垃圾收集器回收它所引用的對象。
  • Phantom reference :?它與上面的3種類型有很大的不同,它的get()?方法始終返回null,即通過這個引用,你甚至都不能獲取它所引用的對象,如果你看它的源碼,它的構(gòu)造器必須要給定一個ReferenceQueue,當然了,你也可以把它設(shè)置為空,但是這樣的引用 一點意義都沒有。我在下文中會結(jié)合Phantom reference的作用來解釋為什么會這樣。
  • 在這一小節(jié)中,我總結(jié)了各個引用的作用。如果大家不太明白,沒關(guān)系,我會在下文中更詳細地解釋它們各自的用法。

    Strong reference

    如果大家對垃圾收集機制有所了解,你們就會知道JVM標記一個對象是否為垃圾是根據(jù)可達性算法。 我們平常寫的代碼其實都是Strong reference,被Strong reference所引用的對象它會保持這個對象到GC roots的可達性,以防被JVM標記為垃圾對象,從而被回收。比如下面的代碼就是一個Strong reference

    String str = new String("hello world");
    • 1
    • 2

    Soft reference

    GC使Java程序員免除管理內(nèi)存的痛苦,但是這并不意味著我們可以不關(guān)心對象的生命同期,如果我們不注意Java對象地生命周期,這很可能會導(dǎo)致Java出現(xiàn)內(nèi)存泄露。

    Object loitering

    在我詳細解釋Soft reference之前,請大家先閱讀下面的這段代碼,仔細想一想它可能出現(xiàn)什么樣的問題?

    public class LeakyChecksum {private byte[] byteArray;public synchronized int getFileChecksum(String fileName) {int len = getFileSize(fileName);if (byteArray == null || byteArray.length < len)byteArray = new byte[len];readFileContents(fileName, byteArray);// calculate checksum and return it} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    對于上面的程序而言,如果我把byteArray字節(jié)數(shù)組放到getFileChecksum方法中完全沒有問題,但是,上面的程序把byteArray字節(jié)數(shù)組從局部變量提升到實例變量會出現(xiàn)很多問題。比如,由于你需要共享byteArray變量,從而你不得不去考慮線程安全問題,而上面的程序在getFileChecksum方法上加上了synchronized?關(guān)鍵字,這大大降低了程序的可擴展性。

    先不去深入討論上面程序出現(xiàn)的其它問題,讓我們來探討一下它出現(xiàn)的內(nèi)存泄露問題。上述代碼的主要功能就是根據(jù)文件的內(nèi)容去計算它的checksum,如果上述代碼的if?條件不成立,它會不斷地重用字節(jié)數(shù)組,而不是重新分配它。除非LeakyChecksum對象被gc,否則這個字節(jié)數(shù)組始終不會被gc,由于程序到它一直是可達的。而且更糟糕的是,隨著程序的不斷運行,這個字節(jié)數(shù)組只會不斷增大,不會減小,它的大小始終都和它處理過的最大的文件的大小一致,這樣很可能會導(dǎo)致JVM更頻繁地GC,降低應(yīng)用程序地性能。大多數(shù)情況下,這個字節(jié)數(shù)組所占的空間要比它實際要用的空間要大,而多余的空間又不能被回收利用,這導(dǎo)致了內(nèi)存泄露。

    Soft references 解決上面的內(nèi)存泄露問題

    對于只被Soft references所引用的對象,我們稱它為softly reachable objects.?只要可得到的內(nèi)存很充足,softly reachable objects 通常不會被gc. JVM要比我們的程序更加了解內(nèi)存的使用情況,如果可得到的內(nèi)存緊張,那么JVM就會頻繁地進行垃圾回收,從而釋放更多的內(nèi)存空間,供我們使用。因此,上述程序的字節(jié)數(shù)組緩存由于一直是可達的,即使在內(nèi)存很緊張的情況下,它也不會被回收掉,這無疑給垃圾收集器更大的壓力,使其更頻繁地GC.

    那么有沒有一種解決方案可以做到這樣呢,如果我們的內(nèi)存很充足,我們就保持這樣的緩存在內(nèi)存中,不被gc; 但是,當我們的內(nèi)存吃緊時,就把它釋放掉。那么大家想一想,誰可以做到這一點呢?答案是JVM,因為它最了解內(nèi)存的使用情況,我們可以借助它的力量來達到我們的目標,而Soft references 可以幫我們Java 程序員借助JVM的力量。下面,讓我們來看看如果用SoftReference?改寫上面的代碼。

    public class CachingChecksum {private SoftReference<byte[]> bufferRef;public synchronized int getFileChecksum(String fileName) {int len = getFileSize(fileName);byte[] byteArray = bufferRef.get();if (byteArray == null || byteArray.length < len) {byteArray = new byte[len];bufferRef.set(byteArray);}readFileContents(fileName, byteArray);// calculate checksum and return it} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    從上面的代碼我們可以看出,一旦走出if?語句,字節(jié)數(shù)組對象就只被Soft references 所引用,成為了softly reachable objects. 對于垃圾收集器來說,它只會在真正需要內(nèi)存的時候才會去回收softly reachable objects. 現(xiàn)在,如果我們的內(nèi)存不算吃緊,這個字節(jié)數(shù)組buffer會一直保存在內(nèi)存中。在拋出OutOfMemoryError?之前,垃圾收集器一定會clear掉所有的soft references.

    Soft references 與緩存

    從上面的例子中,我們看到了如何用soft reference 去緩存1個對象,然后讓JVM去決定什么時候應(yīng)該把對象從緩存中清除。對于嚴重依賴緩存提升性能的應(yīng)用而言,用Soft references 做緩存并不合適,我們應(yīng)該去找一個更全面的緩存框架去做這件事。但是由于它 “cheap and dirty” 的緩存機制, 對于一些小的應(yīng)用場景,它還是很有吸引力的。

    Weak References

    由于JVM幫我們管理Java程序的內(nèi)存,我們總是希望當一個對象不被使用時,它會被立即回收,即一個對象的邏輯生命周期要與它的實際生命周期相一致。但是有些時候,由于寫程序人的疏忽,沒有注意對象的生命周期,導(dǎo)致對象的實際生命周期要比我們期望它的生命周期要長。這種情況叫做?unintentional object retention.下面我們來看看由實例變量HashMap?導(dǎo)致的內(nèi)存泄露問題。

    HashMap 導(dǎo)致的內(nèi)存泄露問題

    用Map去關(guān)聯(lián)短暫對象的元數(shù)據(jù)很容易出現(xiàn)unintentional object retention?問題。比如你想關(guān)聯(lián)一個Socket連接與用戶的信息,由于你并不能干涉Socket?對象的實現(xiàn),向里面加用戶數(shù)據(jù),因此最常用的做法就是用全局Map來做這樣的事。代碼如下:

    public class SocketManager {private Map<Socket,User> m = new HashMap<Socket,User>();public void setUser(Socket s, User u) {m.put(s, u);}public User getUser(Socket s) {return m.get(s);}public void removeUser(Socket s) {m.remove(s);} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通常情況下,Socket?對象的生命周期要比整個應(yīng)用的生命周期要短,同時,它也會比用到它的方法調(diào)用要長。上述代碼把User?對象的生命周期與Socket?對象綁在一起,因為我們不能準確地知道Socket連接在什么時候被關(guān)閉,所以我們不能手動地去把它從Map中移除。而只要SocketManager?對象不死,HashMap?對象就始終是可達的。這樣就會出現(xiàn)一個問題,就是即使服務(wù)完來自客戶端的請求,Socket已經(jīng)關(guān)閉,但是Socket?和?User?對象一直都不會被gc,它們會一直被保留在內(nèi)存中。如果這樣一直下去,就會導(dǎo)致程序出現(xiàn)內(nèi)存溢出的錯誤。

    用 WeakHashMap 解決問題

    既然上面的代碼有內(nèi)存泄露的問題,我們應(yīng)該如何解決呢?如果有一種手段可以做到,比如: 當Map中Entry的Key不再被使用了,就會把這個Entry自動移除,這樣我們就可以解決上面的問題了。幸運的是,Java團隊給我們提供了一個這樣的類可以做到這點,它就是WeakHashMap?,我們只要把我們的代碼做如下修改就行:

    public class SocketManager {private Map<Socket,User> m = new WeakHashMap<Socket,User>();public void setUser(Socket s, User u) {m.put(s, u);}public User getUser(Socket s) {return m.get(s);} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    哈哈,是不是很簡單,就把HashMap?替換成WeakHashMap?就行了。下面的內(nèi)容引用自Java官方文檔?的說明:

    Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

    理解 Weak references

    WeakHashMap?為什么會有這么神奇的功能,而我們的HashMap?卻沒有呢?下面是WeakHashMap?中的部分源碼:

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {V value;final int hash;Entry<K,V> next;/*** Creates new entry.*/Entry(Object key, V value,ReferenceQueue<Object> queue,int hash, Entry<K,V> next) {super(key, queue);this.value = value;this.hash = hash;this.next = next;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    大家可以看到,它的Entry?繼承了WeakReference?類,而我們的HashMap?卻沒有。再看它的構(gòu)造函數(shù),它的key被Weak references 所引用,但是它的value并沒有。接下來,我來解釋一下Weak references的作用。

    一個只被Weak references所引用的對象,它被稱作weakly reachable object. 而這樣的對象不能阻止垃圾收集器對它的回收。 就像上面的源碼一樣,我們會在構(gòu)造的時候,用Weak references去引用對象,如果被引用的對象沒有被gc,那么可以通過WeakReference?的get()?方法去獲取被引用的對象。但是,如果被引用的對象已經(jīng)被垃圾回收或者有人調(diào)用了WeakReference.clear()?,那么get()?方法將始終返回null. 如果你想用get()?方法返回的結(jié)果,一個最佳的實踐就是你應(yīng)該做一下非空檢查。總之,Weak reference并不會延長對象的生命周期。

    現(xiàn)在我們回到上面那個Socket的問題上,當Socket?對象在其它地方被使用時,它不會被回收,而且我們依然可以用WeakHashMap?的?get()?方法去獲取它相關(guān)聯(lián)的數(shù)據(jù),但是一旦Socket連接被關(guān)閉,即我們不再需要這個對象時,WeakHashMap?將不能阻止Socket?對象被回收,因此這完全達到了我們想要的結(jié)果。下面,讓我們來看看WeakHashMap?中g(shù)et()?方法的源碼:

    public V get(Object key) {Object k = maskNull(key); // 如果給定的key是null,則用NULL_KEYint h = hash(k); // 根據(jù)key算出它的hash值Entry<K,V>[] tab = getTable();int index = indexFor(h, tab.length); // 找到當前hash值所對應(yīng)的bucket下標Entry<K,V> e = tab[index];while (e != null) { // 如果有hash沖突的情況下,會沿著鏈表找下去if (e.hash == h && eq(k, e.get()))return e.value;e = e.next;}return null; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我已經(jīng)把上面的代碼加了相應(yīng)地注釋,相信大家理解起來會很容易。還有一點值得說的就是:由于給定的key已經(jīng)被方法參數(shù)所引用,因此在get()?方法中,key并不會被垃圾回收。如果你想把WeakHashMap?變成線程安全的,你可以簡單地用Collections.synchronizedMap()?把它包裝一下就行。

    Reference queues

    大家現(xiàn)在可以看一看上面構(gòu)造器的源碼,其中的一個參數(shù)對象是ReferenceQueue, 那么問題來了,這個東西是干什么用的呢?再具體說它的作用之前,讓我們來探討一下上面Weak references存在的問題。

    上面我已經(jīng)說過了,只有key是被Weak references所引用的,這樣就會出現(xiàn)一個問題,只要SocketManager?對象不被gc,那么WeakHashMap?對象就不會被gc,然后除非你手動地調(diào)用remove()?方法,不然它里面的Entry?也不會被gc,那么問題來了,即使你的key已經(jīng)被gc了,但是key對應(yīng)的value,整個Entry對象依然會被保留在內(nèi)存中,如果一直這樣下去的話,就會導(dǎo)致內(nèi)存溢出。

    那么,我們?nèi)绾谓鉀Q上面出現(xiàn)的問題呢?按照一個正常人的思路來說,我覺得應(yīng)該去周期性地掃描一下Map,根據(jù)get()?方法是否返回null來判斷當前Entry是否應(yīng)該被清除。但是,如果Map中包含了大量的Entry,你這樣做會效率很低。現(xiàn)在,Reference queues 大顯身手的時候到了。如果在你構(gòu)造Weak references的時候,你給它關(guān)聯(lián)一個ReferenceQueue?對象,那么當一個Reference?對象被clear的時候,它會被放入給定的隊列當中。因此,你只需要從這個隊列中獲取Reference?對象,然后做相應(yīng)地清理工作就行了。

    WeakHashMap?中有個私有方法expungeStaleEntries,下面讓我們來看看它的源碼:

    private void expungeStaleEntries() {for (Object x; (x = queue.poll()) != null; ) { // 遍歷引用隊列synchronized (queue) {@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) x; // Entry對象就是Weak referencesint i = indexFor(e.hash, table.length); // 根據(jù)hash值找到相應(yīng)的bucket下標Entry<K,V> prev = table[i];Entry<K,V> p = prev;while (p != null) { // 在鏈表中找出給定key對應(yīng)的Entry,然后清除指向它的引用Entry<K,V> next = p.next;if (p == e) {if (prev == e)table[i] = next;elseprev.next = next;// Must not null out e.next;// stale entries may be in use by a HashIteratore.value = null; // Help GCsize--;break;}prev = p;p = next;}}} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    對于上面的代碼我已經(jīng)給出了相應(yīng)地注釋,主要就是while?循環(huán)中的算法你需要好好理解一下,其實也不難,它其實就是從鏈表中移除一個節(jié)點。其實大家可以去看一看WeakHashMap?的源碼,它的大部分操作都會調(diào)用這個私有方法,比如上面的get?方法中的getTable?方法。

    至此,我已經(jīng)結(jié)合WeakHashMap?的源碼,把Weak references的知識講完了,相信有了這個強大的武器,大家可以更自如地控制對象地可達性了。

    Phantom References

    Phantom References 與上面的幾個引用存在很大的不同,至少上面的Reference?對象通過它們的get()?方法可以獲取到它們所引用的對象,但是,PhantomReference?的只會返回null. 大家可能會想,既然這個引用連對象都取不到,那要它有什么用呢?如果你去看這個Reference?對象的源碼,你會發(fā)現(xiàn)只有PhantomReference?類的構(gòu)造器必須指定一個ReferenceQueue?對象,而這就是重點,當然了,你也可以把它設(shè)置為null,但是那樣將沒有任何意義。因此,Phantom reference 的唯一作用就是它可以監(jiān)測到對象的死亡,即,當你的對象真正從內(nèi)存中移除時,指向這個對象的PhantomReference?就會被加入到隊列中。 下面是一個有助于你理解PhantomReference?的Demo:

    import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue;public class PhantomReferenceDemo {private final static ReferenceQueue<Object> queue = new ReferenceQueue<>();public static void main(String[] args) {Person p1 = new Person("小明");Person p2 = new Person("小花");Animal a1 = new Animal(p1, "dog");Animal a2 = new Animal(p2, "cat");p1 = null;p2 = null;Runtime.getRuntime().gc();waitMoment(2000); // 給gc點時間收集,有時gc收集速度很快,可以不用加這句代碼,我只不過是保險起見printReferenceQueue(queue);}static class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return name;}}static class Animal extends PhantomReference<Object> {private String name;public Animal(Person referent, String name) {super(referent, queue);this.name = name;}public String getName() {return name;}}private static void waitMoment(long time) {try {Thread.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}private static void printReferenceQueue(ReferenceQueue<Object> rq) {int size = 0;Object obj;while ( ( obj = rq.poll() ) != null ) {Animal a = (Animal) obj;System.out.println(a.getName());size++;}System.out.println("引用隊列大小為: " + size);} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    一旦Person?對象被回收,那么指向它的Animal?對象就會被放進隊列中,大家可以把上面的12,13行代碼注釋掉,看看有什么不同的效果。還有一種場景有可能會用到Phantom reference,比如你已經(jīng)把一張很大的圖片加載到內(nèi)存當中,只有當你確定這張圖片從內(nèi)存移除之后,我才會加載下一張圖片,這樣可以防止內(nèi)存溢出錯誤。

    用Phantom reference 驗證不靠譜的finalize 方法

    在這一小節(jié)中,我讓大家來看看Java中的finalize方法有多“不靠譜”。假設(shè)我現(xiàn)在有1個重寫了finalize 方法的對象obj,它被收集的過程如下:

  • 假設(shè)現(xiàn)在有足夠多的垃圾使得JVM進行g(shù)c,并標記了obj?是不可達的
  • 接著,它會把obj?放到finalization隊列中,等待執(zhí)行它的finalize 方法
  • 如果執(zhí)行完它的finalize方法,再次gc時才會把這個對象回收掉。如果這個方法一直沒有執(zhí)行完,這個對象就一直不會被回收。
  • 上面我只說了大致的過程,更詳細的請參考:When is the finalize() method called in Java?

    下面來看我寫的一段Demo代碼:

    import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue;public class PhantomReferenceDemo2 {private final static ReferenceQueue<Object> queue = new ReferenceQueue<>();public static void main(String[] args) {Person p = new Person();PhantomReference<Person> pr = new PhantomReference<>(p, queue);p = null; // 使Person對象變的不可達// 這次gc會把Person對象標記為不可達的,由于它重寫了finalize,因此它會被放入到finalization隊列Runtime.getRuntime().gc();waitMoment(2000); // 給gc更多的時間去處理,并且去執(zhí)行隊列中的finalize方法Runtime.getRuntime().gc(); // 再次發(fā)起gc,收集Person對象waitMoment(2000); // 給gc更多的時間去處理printReferenceQueue(queue); // 如果Person對象已經(jīng)被回收,這個隊列中應(yīng)該有值}static class Person {@Overrideprotected void finalize() throws Throwable {System.out.println("finalize method in Person");}}private static void waitMoment(long time) {try {Thread.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}private static void printReferenceQueue(ReferenceQueue<Object> rq) {int size = 0;Object obj;while ( ( obj = rq.poll() ) != null ) {System.out.println(obj);size++;}System.out.println("引用隊列大小為: " + size);} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    上面的代碼我已經(jīng)加了很詳細的注釋,這里我們用Phantom reference 去監(jiān)控Person?對象的存活狀態(tài)。大家再看看下面的代碼:

    import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue;public class PhantomReferenceDemo2 {private final static ReferenceQueue<Object> queue = new ReferenceQueue<>();public static void main(String[] args) {BlockFinalization bf = new BlockFinalization();PhantomReference<BlockFinalization> prbf = new PhantomReference<>(bf, queue);bf = null; // 使BlockFinalization對象變的不可達// 我讓BlockFinalization對象中的finalize方法睡了1000秒,這樣會導(dǎo)致主線程即使結(jié)束,finalize方法也不會執(zhí)行完Runtime.getRuntime().gc();Person p = new Person();PhantomReference<Person> pr = new PhantomReference<>(p, queue);p = null; // 使Person對象變的不可達// 這次會把Person對象放入到finalization隊列Runtime.getRuntime().gc();waitMoment(2000);Runtime.getRuntime().gc();waitMoment(2000);// 如果這2個對象中的finalize方法不被執(zhí)行完,它們都不會被回收,根據(jù)隊列輸出的值就可以看出來了printReferenceQueue(queue);}static class BlockFinalization {@Overrideprotected void finalize() throws Throwable {System.out.println("finalize method in BlockFinalization");Thread.sleep(1000000);}} }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    上面的代碼有時會很詭異,在我的Ubuntu系統(tǒng)上,有時會出現(xiàn)先執(zhí)行Person?中的finalize?方法的可能,這樣就會導(dǎo)致Person?會被垃圾回收,如果是BlockFinalization?對象中的方法先被執(zhí)行,那么2個對象都不會被回收。大家可以把第31行代碼注釋掉,看看效果。從上面的例子可以看到,finalize方法是非常不靠譜的,它不但有可能會導(dǎo)致對象無法回收,而且還有可能出現(xiàn)在程序的生命周期之內(nèi)不被執(zhí)行的可能。

    所以建議大家千萬不要使用這個方法,如果你非要使用它,我建議你去看Effective Java中的Item 7: Avoid finalizers ,這本書中介紹了關(guān)于使用它的2個場景,以及一些技巧。

    垃圾收集器如何對待 Reference 對象

    如果你去看WeakReference,?SoftReference,?PhantomReference的源碼,你會發(fā)現(xiàn)它們都繼承了抽象的?Reference?類。那么現(xiàn)在我們來看看當垃圾收集器遇到?Reference?對象時,是如何對待它們的?

    當垃圾收集器在追蹤heap,遇到Reference?對象時,它并不會標記Reference?對象所引用的對象。它會把遇到的Reference?對象放到一個隊列中,追蹤heap過后,它會標識出softly reachable objects。垃圾收集器會基于當前GC回收的內(nèi)存大小和其它的一些原則,來決定soft references是否需要被clear.?大家可以看一看Reference?類的clear?方法。

    如果垃圾收集器決定clear這些soft references ,并且這些soft references有相應(yīng)地ReferenceQueue?,那么這些被clear 的Reference?對象會被放到ReferenceQueue?隊列中。注意:clear?Reference?對象并把它放入到隊列中是發(fā)生在被引用對象的finalization 或 garbage collection 實際發(fā)生之前。

    如果垃圾收集器并不打算clear這些Reference?對象,那么它們對應(yīng)地softly reachable objects會被當作GC roots,并用這些GC roots繼續(xù)追蹤heap,使得這些通過soft references可達的對象被標記。

    處理完soft references 過后,接下來會找出weakly reachable objects. Weak references 會被直接clear掉,然后放到對應(yīng)地?ReferenceQueue中。

    所有的Reference?類型都會在放入ReferenceQueue?前被clear掉,因此后續(xù)的處理你將不可能訪問到Reference類型引用的對象。

    由于要特殊地對待Reference?類型,因此在垃圾收集的過程中,無疑會增加額外的開銷。如果Reference 對象不被放到對應(yīng)地ReferenceQueue?中,那么它本身也會被回收的,而且它可能會在它的引用對象回收之前被回收。

    參考資料

    Plugging memory leaks with soft references

    Plugging memory leaks with weak references

    Understanding Weak References Blog

    What is the difference between a soft reference and a weak reference in Java?

    When is the finalize() method called in Java?

    Have you ever used Phantom reference in any project?

    What is the difference between a soft reference and a weak reference in Java?

    版權(quán)聲明:轉(zhuǎn)載請注明來源,謝謝 https://blog.csdn.net/xlinsist/article/details/57089288

    總結(jié)

    以上是生活随笔為你收集整理的深入理解java中的Soft references amp;amp; Weak references amp;amp; Phantom reference的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 国产毛片在线 | 无码熟妇αⅴ人妻又粗又大 | 国产精品97 | 91偷拍网站 | 久久国产精品久久久 | 在线成人免费视频 | 娇小萝被两个黑人用半米长 | 国产精品不卡av | 在线成人av网站 | va在线看| 欧美亚洲另类小说 | 欧美激情精品久久久久久蜜臀 | 成人免费精品 | 午夜视频久久 | 国产主播av | 超91在线 | 欧美成年人视频在线观看 | 香蕉久久av一区二区三区 | 九热精品 | eeuss鲁片一区二区三区在线观看 | 亚洲精品永久免费 | 欧美精品一区二区三区四区五区 | 伊人久久一区二区三区 | 色91精品久久久久久久久 | 亚洲一区免费观看 | 国产精品一区在线播放 | 一级a性色生活片久久毛片 爱爱高潮视频 | 天天舔天天操天天干 | 黄色的网站免费看 | 亚洲AV无码精品久久一区二区 | 国产精品无码专区 | 中文字幕在线视频免费播放 | 这里只有精品免费视频 | 成人黄色片免费看 | 人人妻人人澡人人爽人人欧美一区 | 九九九网站 | 欧美综合视频 | 蜜乳av网站| 丁香婷婷一区二区三区 | 99精品网 | 亚洲视频一 | 欧美久操| 亚洲逼图| 亚洲人人夜夜澡人人爽 | 神马午夜dy888| 欧美精品一区三区 | 综合网激情 | 高清中文字幕 | 亚洲免费a | 在线伊人网 | 毛片成人 | 黄色片美女 | 亚洲男人的天堂网 | 色天堂视频 | 久久国产黄色片 | 日本在线高清 | 成人区一区二区 | xx视频在线| 草免费视频 | 日韩视频免费在线播放 | 香蕉视频性 | 一道本在线观看 | 亚洲一区二区三区影视 | 少妇一级淫免费观看 | 成人免费毛片网 | 在线观看成年人视频 | 久久青草免费视频 | 午夜视频在线免费播放 | 成人欧美一区二区三区在线播放 | 99久久免费看精品国产一区 | av中文字幕网站 | 欧美激情免费在线 | 久久久久久久久久久久久久国产 | 青青草91视频| 亚洲激情六月 | 国产精品视频你懂的 | 亚洲精品少妇 | 成人在线观看免费爱爱 | 中文字幕在线日本 | 男人天堂b | 日韩国产欧美精品 | 亚洲精品国产a | 欧美丰满熟妇xxxxx | ,国产精品国产三级国产 | 久久久精品一区二区涩爱 | 欧美激情视频在线观看 | 欧美a级成人淫片免费看 | 人妻无码中文字幕免费视频蜜桃 | 国产综合精品一区二区三区 | 伊人99 | 特级毛片av | www.日批 | 自拍偷拍精品视频 | 日韩理论在线观看 | 波多野结衣免费视频观看 | 午夜成年人视频 | 日韩欧美麻豆 | 国产精品熟女久久久久久 | 青青草手机在线观看 |