国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Java 理論和實踐: 用軟引用阻止內存泄漏

2019-11-18 11:28:48
字體:
來源:轉載
供稿:網友

  在 java 理論和實踐 的 前一期文章 中,Java™ 清潔工程師 Brian Goetz 探究了弱引用(weak references),它讓您警告垃圾收集器,您想要維護一個對象的引用,而不會阻止該對象被垃圾收集。在本期文章中,他將解釋 Reference 對象的另外一種形式,即軟引用(soft references),用于幫助垃圾收集器治理內存使用和消除潛在的內存泄漏。
垃圾收集可以使 Java 程序不會出現內存泄漏,至少對于比較狹窄的 “內存泄漏” 定義來說如此,但是這并不意味著我們可以完全忽略 Java 程序中的對象生存期(lifetime)問題。當我們沒有對對象生命周期(lifecycle)引起足夠的重視或者破壞了治理對象生命周期的標準機制時,Java 程序中通常就會出現內存泄漏。例如,上一次 我們看到了,不能劃分對象的生命周期會導致,在試圖將元數據關聯到瞬時對象時出現意外的對象保持。還有一些其他的情況可以類似地忽略或破壞對象生命周期治理,并導致內存泄漏。

對象游離

一種形式的內存泄漏有時候叫做對象游離(object loitering),是通過清單 1 中的 LeakyChecksum 類來說明的,清單 1 中有一個 getFileChecksum() 方法用于計算文件內容的校驗和。getFileChecksum() 方法將文件內容讀取到緩沖區中以計算校驗和。一種更加直觀的實現簡單地將緩沖區作為 getFileChecksum() 中的本地變量分配,但是該版本比那樣的版本更加 “聰明”,不是將緩沖區緩存在實例字段中以減少內存 churn。該 “優化”通常不帶來預期的好處;對象分配比很多人期望的更便宜。(還要注重,將緩沖區從本地變量提升到實例變量,使得類若不帶有附加的同步,就不再是線程安全的了。直觀的實現不需要將 getFileChecksum() 聲明為 synchronized,并且會在同時調用時提供更好的可伸縮性。) 


清單 1. 展示 “對象游離” 的類

// BAD CODE - DO NOT EMULATE
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
    }
}
 


這個類存在很多的問題,但是我們著重來看內存泄漏。緩存緩沖區的決定很可能是根據這樣的假設得出的,即該類將在一個程序中被調用許多次,因此它應該更加有效,以重用緩沖區而不是重新分配它。但是結果是,緩沖區永遠不會被釋放,因為它對程序來說總是可及的(除非 LeakyChecksum 對象被垃圾收集了)。更壞的是,它可以增長,卻不可以縮小,所以 LeakyChecksum 將永久保持一個與所處理的最大文件一樣大小的緩沖區。退一萬步說,這也會給垃圾收集器帶來壓力,并且要求更頻繁的收集;為計算未來的校驗和而保持一個大型緩沖區并不是可用內存的最有效利用。

LeakyChecksum 中問題的原因是,緩沖區對于 getFileChecksum() 操作來說邏輯上是本地的,但是它的生命周期已經被人為延長了,因為將它提升到了實例字段。因此,該類必須自己治理緩沖區的生命周期??而不是讓 JVM 來治理。 


軟引用

在 前一期文章 中我們看到了,弱引用如何可以給應用程序提供當對象被程序使用時另一種到達該對象的方法,但是不會延長對象的生命周期。Reference 的另一個子類 ?? 軟引用 ?? 可滿足一個不同卻相關的目的。其中弱引用答應應用程序創建不妨礙垃圾收集的引用,軟引用答應應用程序通過將一些對象指定為 “eXPendable” 而利用垃圾收集器的幫助。盡管垃圾收集器在找出哪些內存在由應用程序使用哪些沒在使用方面做得很好,但是確定可用內存的最適當使用還是取決于應用程序。假如應用程序做出了不好的決定,使得對象被保持,那么性能會受到影響,因為垃圾收集器必須更加辛勤地工作,以防止應用程序消耗掉所有內存。

高速緩存是一種常見的性能優化,答應應用程序重用以前的計算結果,而不是重新進行計算。高速緩存是 CPU 利用和內存使用之間的一種折衷,這種折衷理想的平衡狀態取決于有多少內存可用。若高速緩存太少,則所要求的性能優勢無法達到;若太多,則性能會受到影響,因為太多的內存被用于高速緩存上,導致其他用途沒有足夠的可用內存。因為垃圾收集器比應用程序更適合決定內存需求,所以應該利用垃圾收集器在做這些決定方面的幫助,這就是件引用所要做的。 

假如一個對象惟一剩下的引用是弱引用或軟引用,那么該對象是軟可及的(softly reachable)。垃圾收集器并不像其收集弱可及的對象一樣盡量地收集軟可及的對象,相反,它只在真正 “需要” 內存時才收集軟可及的對象。軟引用對于垃圾收集器來說是這樣一種方式,即 “只要內存不太緊張,我就會保留該對象。但是假如內存變得真正緊張了,我就會去收集并處理這個對象。” 垃圾收集器在可以拋出 OutOfMemoryError 之前需要清除所有的軟引用。 

通過使用一個軟引用來治理高速緩存的緩沖區,可以解決 LeakyChecksum 中的問題,如清單 2 所示。現在,只要不是非凡需要內存,緩沖區就會被保留,但是在需要時,也可被垃圾收集器回收: 


清單 2. 用軟引用修復 LeakyChecksum

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
    }
}
 


一種廉價的緩存

CachingChecksum 使用一個軟引用來緩存單個對象,并讓 JVM 處理從緩存中取走對象時的細節。類似地,軟引用也經常用于 GUI 應用程序中,用于緩存位圖圖形。是否可使用軟引用的要害在于,應用程序是否可從大量緩存的數據恢復。 

假如需要緩存不止一個對象,您可以使用一個 Map,但是可以選擇如何使用軟引用。您可以將緩存作為 Map<K, SoftReference<V>> 或 SoftReference<Map<K,V>> 治理。后一種選項通常更好一些,因為它給垃圾收集器帶來的工作更少,并且答應在非凡需要內存時以較少的工作回收整個緩存。弱引用有時會錯誤地用于取代軟引用,用于構建緩存,但是這會導致差的緩存性能。在實踐中,弱引用將在對象變得弱可及之后被很快地清除掉 ?? 通常是在緩存的對象再次用到之前 ?? 因為小的垃圾收集運行得很頻繁。 

對于在性能上非常依靠高速緩存的應用程序來說,軟引用是一個不管用的手段,它確實不能取代能夠提供靈活終止期、復制和事務型高速緩存的復雜的高速緩存框架。但是作為一種 “廉價(cheap and dirty)” 的高速緩存機制,它對于降低價格是很有吸引力的。 

正如弱引用一樣,軟引用也可創建為具有一個相關的引用隊列,引用在被垃圾收集器清除時進入隊列。引用隊列對于軟引用來說,沒有對弱引用那么有用,但是它們可以用于發出治理警報,說明應用程序開始缺少內存。 

垃圾收集器如何處理 References

弱引用和軟引用都擴展了抽象的 Reference 類(虛引用(phantom references)也一樣,這將在以后的文章中介紹)。引用對象被垃圾收集器非凡地看待。垃圾收集器在跟蹤堆期間碰到一個 Reference 時,不會標記或跟蹤該引用對象,而是在已知活躍的 Reference 對象的隊列上放置一個 Reference。在跟蹤之后,垃圾收集器就識別軟可及的對象 ?? 這些對象上除了軟引用外,沒有任何強引用。垃圾收集器然??根據當前收集所回收的內存總量和其他策略考慮因素,判定軟引用此時是否需要被清除。將被清除的軟引用假如具有相應的引用隊列,就會進入隊列。其余的軟可及對象(沒有清除的對象)然后被看作一個根集(root set),堆跟蹤繼續使用這些新的根,以便通過活躍的軟引用而可及的對象能夠被標記。 

處理軟引用之后,弱可及對象的集合被識別 ?? 這樣的對象上不存在強引用或軟引用。這些對象被清除和加入隊列。所有 Reference 類型在加入隊列之前被清除,所以處理事后檢查(post-mortem)清除的線程永遠不會具有 referent 對象的訪問權,而只具有 Reference 對象的訪問權。因此,當 References 與引用隊列一起使用時,通常需要細分適當的引用類型,并將它直接用于您的設計中(與 WeakHashMap 一樣,它的 Map.Entry 擴展了 WeakReference)或者存儲對需要清除的實體的引用。 

引用處理的性能成本

引用對象給垃圾收集過程帶來了一些附加的成本。每一次垃圾收集,都必須構造活躍 Reference 對象的一個列表,而且每個引用都必須做適當的處理,這給每次收集添加了一些每個 Reference 的開銷,而不管該 referent 此時是否被收集。Reference 對象本身服從于垃圾收集,并且可在 referent 之前被收集,在這樣的情況下,它們沒有加入隊列。 

基于數組的集合

當數組用于實現諸如堆棧或環形緩沖區之類的數據結構時,會出現另一種形式的對象游離。清單 3 中的 LeakyStack 類展示了用數組實現的堆棧的實現。在 pop() 方法中,在頂部指針遞減之后,elements 仍然會保留對將彈出堆棧的對象的引用。這意味著,該對象的引用對程序來說仍然可及(即使程序實際上不會再使用該引用),這會阻止該對象被垃圾收集,直到該位置被未來的 push() 重用。 


清單 3. 基于數組的集合中的對象游離

public class LeakyStack {
    private Object[] elements = new Object[MAX_ELEMENTS];
    private int size = 0;
    
    public void push(Object o) { elements[size++] = o; }
    
    public Object pop() { 
        if (size == 0)
            throw new EmptyStackException();
        else {
            Object result = elements[--size];
            // elements[size+1] = null;
            return result;
        } 
    }
}
 


修復這種情況下的對象游離的方法是,當對象從堆棧彈出之后,就消除它的引用,如清單 3 中注釋掉的行所示。但是這種情況 ?? 由類治理其自己的內存 ?? 是一種非常少見的情況,即顯式地消除不再需要的對象是一個好主意。大部分時候,認為不應該使用的強行消除引用根本不會帶來性能或內存使用方面的收益,通常是導致更差的性能或者 NullPointerException。該算法的一個鏈接實現不會存在這個問題。在鏈接實現中,鏈接節點(以及所存儲的對象的引用)的生命期將被自動與對象存儲在集合中的期間綁定在一起。弱引用可用于解決這個問題 ?? 維護弱引用而不是強引用的一個數組 ?? 但是在實際中,LeakyStack 治理它自己的內存,因此負責確保對不再需要的對象的引用被清除。使用數組來實現堆?;蚓彌_區是一種優化,可以減少分配,但是會給實現者帶來更大的負擔,需要仔細地治理存儲在數組中的引用的生命期。 


結束語

與弱引用一樣,軟引用通過利用垃圾收集器在作出緩存回收決策方面的幫助,有助于防止應用程序出現對象游離。只有當應用程序可以忍受大量軟引用的對象時,軟引用才適合使用。 


參考資料:

您可以參閱本文在 developerWorks 全球站點上的 英文原文。


“Java 理論與實踐: 用弱引用堵住內存泄漏”:上一期的 Java 理論和實踐 介紹了 SoftReference 的同類 WeakReference。


“關注性能: 調優垃圾收集”:Kirk Pepperdine 和 Jack Shirazi 論證了,即使緩慢的內存泄漏最終也會給垃圾收集器帶來巨大的壓力。


Reference objects and garbage collection:這篇論文出自 Sun,寫于 Reference 對象被添加到類庫之后不久,描述了垃圾收集器如何對待 Reference 對象。

Java 理論和實踐:Brian Goetz 撰寫的完整系列。

Java 技術專區:數百篇關于 Java 編程各個方面的文章。

獲得產品和技術

JTune:這個免費的 JTune 工具可以利用 GC 日志并以圖表形式顯示堆大小、GC 期間和其他有用的內存治理數據。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 清涧县| 盐山县| 当阳市| 资阳市| 兰州市| 四子王旗| 特克斯县| 新蔡县| 花莲市| 长沙市| 道真| 慈利县| 渭源县| 都江堰市| 巴东县| 色达县| 邵武市| 澎湖县| 泗洪县| 巴楚县| 寿光市| 凤庆县| 洛隆县| 铁力市| 信宜市| 石柱| 淮南市| 麻阳| 卢龙县| 安达市| 镇坪县| 田阳县| 郴州市| 阿鲁科尔沁旗| 紫阳县| 闸北区| 当涂县| 永和县| 来安县| 望奎县| 和田市|