弱,弱,最弱,利用专业参考来利用垃圾收集器
何時以及何時不使用Java中的專家引用
弱引用,軟引用和幻像引用是危險且強(qiáng)大的。 如果以錯誤的方式使用它們,則會破壞JVM性能。 但是,如果使用正確的方法,它們可以大大提高性能和程序清晰度。
弱引用和軟引用在這三個中更為明顯。 實際上,它們幾乎是同一件事! 這個想法很簡單,就是將它們用于訪問對象,但不會阻止垃圾回收器回收該對象:
您發(fā)現(xiàn)故意的錯誤嗎? 這是一個容易錯過的東西,并且可能不會在單元測試中顯示。 正是這種問題使我說:
僅在絕對必要且可能甚至沒有的情況下,才使用弱/軟引用。
當(dāng)JVM承受內(nèi)存壓力時,它可能會在弱引用中的get方法的第一次和第二次調(diào)用之間回收該對象。 當(dāng)在null上調(diào)用toString方法時,這將導(dǎo)致程序引發(fā)null指針異常。 該代碼的正確形式為:
Object x=x.get(); // Now we have null xor a hard reference to // the object if(z==null){System.out.println("The object has gone away"); }else{System.out.println("The object is " + z.toString()); }我們?yōu)槭裁匆麄?#xff1f;
我們還沒有完全談到為什么它們真的非常危險。 為此,我們需要了解為什么我們可能需要它們以及為什么我們需要它們。 在兩種常見的情況下,弱引用和軟引用似乎是一個好主意(我們將在稍后介紹軟和弱引用之間的區(qū)別)。 首先是某種形式的RAM緩存。
它是這樣的:
我們有一些數(shù)據(jù),例如客戶詳細(xì)信息,存儲在數(shù)據(jù)庫中。 我們一直在尋找它,這很慢。 我們可以做的是將這些數(shù)據(jù)緩存在RAM中。 但是,最終RAM將填充名稱和地址,并且JVM拋出OutOfMemoryError。 解決方案是將名稱和地址存儲在只能弱訪問的對象中。 像這樣:
ConcurrentHasMap>String,WeakReference>CustomerInfo<< cache=new ConcurrentHashMap><(); ... CustomerInfo currentCustomer=cache.get(customerName); if(currentCustomer==null){currentCustomer=reloadCachesEntry(customerName); }這種無害的小模式非常有能力使巨型的JVM崩潰。 該模式正在使用JVM的垃圾回收器來管理內(nèi)存中的緩存。 垃圾收集器從未被設(shè)計為這樣做。 該模式通過用難以到達(dá)的對象填充內(nèi)存來濫用垃圾收集器,這些對象使JVM耗盡了堆空間。 當(dāng)JVM的內(nèi)存不足時,它必須遍歷其堆中的所有引用(弱引用,軟引用和其他引用)并回收RAM。 這是昂貴的并且顯示為處理成本。 在具有許多處理器核心的超大型JVM實例上,情況甚至更糟,因為垃圾收集器很可能最終不得不執(zhí)行“停止世界”的完整循環(huán),從而將性能降低到單個核心級別!
我并不是說內(nèi)存緩存技術(shù)不是一個好主意。 哦,不,這是個好主意。 但是,僅將其扔到垃圾收集器上并且不期望麻煩是非常糟糕的設(shè)計選擇。
弱vs軟
有什么區(qū)別? 好吧,真的有很多。 在某些JVM(例如客戶端托管JVM)上,但弱引用可能會標(biāo)記為優(yōu)先垃圾收集。 換句話說,與其他內(nèi)存相比,垃圾回收器應(yīng)更努力地從它們引用的對象圖中回收內(nèi)存(并且不引用軟或硬引用)。 軟引用對他們沒有這個想法。 但是,這只是某些JVM上的可選想法,不能被依賴,無論如何這都是一個壞主意。 我建議始終使用軟引用或弱引用,并堅持使用它。 選擇您喜歡的聲音。 我更喜歡WeakReference這個名字,所以傾向于使用它。
還有一個區(qū)別。 一個由軟引用和弱引用而不是硬引用引用的對象可能會遇到這樣的情況,即仍然可以從弱引用的.get()方法中獲取,而不能從軟引用的.get()方法中獲取。 反之亦然,反之亦然。 依賴此行為的代碼可能是錯誤的標(biāo)題。
弱引用的良好用法
確實存在。 弱引用對于跟蹤在其他地方使用的對象很有用。 一個示例來自Sonic Field(音頻處理程序包)。 在此示例中,文件中的“插槽”包含音頻數(shù)據(jù),并與內(nèi)存中的對象相關(guān)聯(lián)。 該模型不使用弱引用來引用數(shù)據(jù)的內(nèi)存中副本。 在內(nèi)存對象中使用插槽。 弱引用用于允許文件管理系統(tǒng)重用插槽。
使用插槽的代碼不需要(也不需要)與磁盤空間的管理有關(guān)。 文件管理器很關(guān)心這樣做。 文件管理器對使用插槽的對象有較弱的引用。 當(dāng)請求一個新的插槽時,文件管理器將檢查通過弱引用引用的所有現(xiàn)有插槽,這些弱引用已被回收(因此從get方法返回null)。 如果找到這樣的引用,則可以重用該插槽。
自動通知回收
有時我們可能想知道何時已收回弱引用或軟引用(或其他類型的幻像)。 這可以通過排隊系統(tǒng)來完成。 我們可以使用參考隊列來做到這一點:
WeakReference(T referent, ReferenceQueue<? super T> q)我們做這樣的事情:
ReferenceQueue junkQ = new ReferenceQueue<>(); .... WeakReference<FileSlot> mySlot=new WeakReference<>(aSlot); .... // In a different thread - make sure it is daemon! WeakReference<FileSlot> isDead; while(true){isDead = junkQ.remove();// Take some action based on the fact it is dead// But - it might not be dead - see end of post :( ... }但是,請記住,當(dāng)弱引用在junkQ上結(jié)束時,對其調(diào)用.get()將返回null。 如果您必須存儲信息以允許您感興趣的任何操作發(fā)生在其他地方(例如,ConcurrentHashMap,其中引用是關(guān)鍵),
那么什么是幻影引用?
幻像引用是一種,當(dāng)您需要它們時,您確實需要它們。 但是從表面上看,它們似乎毫無用處。 您會看到,每當(dāng)在幻影引用上調(diào)用.get()時,始終會返回null。 永遠(yuǎn)不可能使用幻像引用來引用它所引用的對象。 好吧-這不是真的。 有時我們可以通過JNI來實現(xiàn)這一目標(biāo),但我們絕對不應(yīng)該這樣做。
考慮一下您在與Java對象關(guān)聯(lián)的JNI中分配本機(jī)內(nèi)存的情況。 JDK的noi包中的DirectBuffers使用的就是這種模型。 這是我在大型商業(yè)項目中反復(fù)使用的東西。
那么,我們?nèi)绾位厥毡緳C(jī)內(nèi)存?
對于類似文件的系統(tǒng),可以說直到關(guān)閉文件才回收內(nèi)存。 這將資源管理的責(zé)任放在程序員的肩膀上; 這正是程序員對文件之類的期望。 但是,對于重量較輕的對象,我們的程序員不喜歡考慮資源管理-垃圾收集器可以為我們做這些事情。
我們可以將代碼放在終結(jié)器中,終結(jié)器調(diào)用JNI代碼來回收內(nèi)存。 這是很糟糕的(就像致命的那樣),因為JVM幾乎保證它們將調(diào)用終結(jié)器。 所以,不要那樣做! 但是,幻象引用可以解救! 首先,我們需要理解“幻影可到達(dá)”:只有在無法通過任何其他種類的參考(硬,弱或軟)到達(dá)幻像引用時,它才會被排隊。 在這一點上,幻影參考可以入隊。 如果對象具有終結(jié)器,則它將被忽略或運行。 但它不會“將物體帶回生活”。 幻影可到達(dá)對象對于JNI本機(jī)代碼(或任何其他代碼)來說是“安全的”,可以根據(jù)資源回收資源。
因此,帶有幻像引用的代碼如下所示:
ReferenceQueue<FileSlot> junkQ = new ReferenceQueue<>(); .... Phantom<FileSlot> mySlot=new Phantom<>(aSlot); .... // In a different thread - make sure it is daemon! Phantom<FileSlot> isDead; while(true){isDead=junkQ.remove();long handle=lookUpHandle(isDead);cleanNativeMemory(handle); }在這種模式下,我們保留了一個句柄,本機(jī)代碼可使用該句柄在Java中的結(jié)構(gòu)(可能是另一個哈希圖)中查找和回收資源。 當(dāng)我們完全確定Java對象無法重生時-它是幻影可到達(dá)的(例如,幽靈-我們可以安全地回收本機(jī)資源。如果您的代碼使用sun.misc.unsafe進(jìn)行了其他“頑皮的”事情(對于有關(guān)使用此技術(shù)的完整示例,請參見這篇文章 。
關(guān)于幻影引用的最后思考。 從技術(shù)上講,可以使用弱引用來實現(xiàn)與上述相同的模式。 但是,這并不是弱引用的目的,并且這種模式會濫用它們。 幻象引用絕對保證對象確實已死,因此可以回收資源。 僅舉一個例子,理論上就有可能使弱引用入隊,然后通過終結(jié)器將對象恢復(fù)生命,因為終結(jié)隊列的運行速度比弱參考隊列慢。 這種幻影恐怖故事不可能在幻影引用中發(fā)生。
有一個小問題,這是JVM設(shè)計的弱點。 也就是說,JNI全局弱引用類型與幻像引用具有未定義的關(guān)系。 有人建議即使將對象排隊為幻像引用,也可以使用全局弱引用來獲取對象。 這是對JVM的一種特定實現(xiàn)的怪癖,切勿使用。
參考: 弱,更弱,最弱,利用垃圾收集器 ,以及來自Java日歷日歷博客上我們JCG合作伙伴 Alexander Turner的專業(yè)參考 。
翻譯自: https://www.javacodegeeks.com/2012/12/weak-weaker-weakest-harnessing-the-garbage-collector-with-specialist-references.html
總結(jié)
以上是生活随笔為你收集整理的弱,弱,最弱,利用专业参考来利用垃圾收集器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Maven Failsafe和Tes
- 下一篇: 使用Camel在来自不同来源的Solr中