青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

woaidongmao

文章均收錄自他人博客,但不喜標題前加-[轉貼],因其丑陋,見諒!~
隨筆 - 1469, 文章 - 0, 評論 - 661, 引用 - 0
數據加載中……

Java 理論與實踐: 用弱引用堵住內存泄漏

雖然用 Java? 語言編寫的程序在理論上是不會出現內存泄漏的,但是有時對象在不再作為程序的邏輯狀態的一部分之后仍然不被垃圾收集。本月,負責保障應用程序健康的工程師 Brian Goetz 探討了無意識的對象保留的常見原因,并展示了如何用弱引用堵住泄漏。

要讓垃圾收集(GC)回收程序不再使用的對象,對象的邏輯 生命周期(應用程序使用它的時間)和對該對象擁有的引用的實際 生命周期必須是相同的。在大多數時候,好的軟件工程技術保證這是自動實現的,不用我們對對象生命周期問題花費過多心思。但是偶爾我們會創建一個引用,它在內存中包含對象的時間比我們預期的要長得多,這種情況稱為無意識的對象保留(unintentional object retention

全局 Map 造成的內存泄漏

無意識對象保留最常見的原因是使用 Map 將元數據與臨時對象(transient object)相關聯。假定一個對象具有中等生命周期,比分配它的那個方法調用的生命周期長,但是比應用程序的生命周期短,如客戶機的套接字連接。需要將一些元數據與這個套接字關聯,如生成連接的用戶的標識。在創建 Socket 時是不知道這些信息的,并且不能將數據添加到 Socket 對象上,因為不能控制 Socket 類或者它的子類。這時,典型的方法就是在一個全局 Map 中存儲這些信息,如清單 1 中的 SocketManager 類所示:


清單 1. 使用一個全局 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);
    }
}
SocketManager socketManager;
...
socketManager.setUser(socket, user);

 

這種方法的問題是元數據的生命周期需要與套接字的生命周期掛鉤,但是除非準確地知道什么時候程序不再需要這個套接字,并記住從 Map 中刪除相應的映射,否則,Socket User 對象將會永遠留在 Map 中,遠遠超過響應了請求和關閉套接字的時間。這會阻止 Socket User 對象被垃圾收集,即使應用程序不會再使用它們。這些對象留下來不受控制,很容易造成程序在長時間運行后內存爆滿。除了最簡單的情況,在幾乎所有情況下找出什么時候 Socket 不再被程序使用是一件很煩人和容易出錯的任務,需要人工對內存進行管理。

 

clip_image002
clip_image004

clip_image005

 

找出內存泄漏

程序有內存泄漏的第一個跡象通常是它拋出一個 OutOfMemoryError,或者因為頻繁的垃圾收集而表現出糟糕的性能。幸運的是,垃圾收集可以提供能夠用來診斷內存泄漏的大量信息。如果以 -verbose:gc 或者 -Xloggc 選項調用 JVM,那么每次 GC 運行時在控制臺上或者日志文件中會打印出一個診斷信息,包括它所花費的時間、當前堆使用情況以及恢復了多少內存。記錄 GC 使用情況并不具有干擾性,因此如果需要分析內存問題或者調優垃圾收集器,在生產環境中默認啟用 GC 日志是值得的。

有工具可以利用 GC 日志輸出并以圖形方式將它顯示出來,JTune 就是這樣的一種工具(請參閱 參考資料)。觀察 GC 之后堆大小的圖,可以看到程序內存使用的趨勢。對于大多數程序來說,可以將內存使用分為兩部分:baseline 使用和 current load 使用。對于服務器應用程序,baseline 使用就是應用程序在沒有任何負荷、但是已經準備好接受請求時的內存使用,current load 使用是在處理請求過程中使用的、但是在請求處理完成后會釋放的內存。只要負荷大體上是恒定的,應用程序通常會很快達到一個穩定的內存使用水平。如果在應用程序已經完成了其初始化并且負荷沒有增加的情況下,內存使用持續增加,那么程序就可能在處理前面的請求時保留了生成的對象。

清單 2 展示了一個有內存泄漏的程序。MapLeaker 在線程池中處理任務,并在一個 Map 中記錄每一項任務的狀態。不幸的是,在任務完成后它不會刪除那一項,因此狀態項和任務對象(以及它們的內部狀態)會不斷地積累。


清單 2. 具有基于 Map 的內存泄漏的程序

 
public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map<Task, TaskStatus> taskStatus 
        = Collections.synchronizedMap(new HashMap<Task, TaskStatus>());
    private Random random = new Random();
    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };
    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];
        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }
    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}

 

1 顯示 MapLeaker GC 之后應用程序堆大小隨著時間的變化圖。上升趨勢是存在內存泄漏的警示信號。(在真實的應用程序中,坡度不會這么大,但是在收集了足夠長時間的 GC 數據后,上升趨勢通常會表現得很明顯。)


1. 持續上升的內存使用趨勢
clip_image007

確信有了內存泄漏后,下一步就是找出哪種對象造成了這個問題。所有內存分析器都可以生成按照對象類進行分解的堆快照。有一些很好的商業堆分析工具,但是找出內存泄漏不一定要花錢買這些工具 —— 內置的 hprof 工具也可完成這項工作。要使用 hprof 并讓它跟蹤內存使用,需要以 -Xrunhprof:heap=sites 選項調用 JVM

清單 3 顯示分解了應用程序內存使用的 hprof 輸出的相關部分。(hprof 工具在應用程序退出時,或者用 kill -3 或在 Windows 中按 Ctrl+Break 時生成使用分解。)注意兩次快照相比,Map.EntryTask int[] 對象有了顯著增加。

請參閱 清單 3

清單 4 展示了 hprof 輸出的另一部分,給出了 Map.Entry 對象的分配點的調用堆棧信息。這個輸出告訴我們哪些調用鏈生成了 Map.Entry 對象,并帶有一些程序分析,找出內存泄漏來源一般來說是相當容易的。


清單 4. HPROF 輸出,顯示 Map.Entry 對象的分配點

 
TRACE 300446:
        java.util.HashMap$Entry.<init>(<Unknown Source>:Unknown line)
        java.util.HashMap.addEntry(<Unknown Source>:Unknown line)
        java.util.HashMap.put(<Unknown Source>:Unknown line)
        java.util.Collections$SynchronizedMap.put(<Unknown Source>:Unknown line)
        com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
        com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)

 

clip_image002
clip_image004

clip_image005

 

弱引用來救援了

SocketManager 的問題是 Socket-User 映射的生命周期應當與 Socket 的生命周期相匹配,但是語言沒有提供任何容易的方法實施這項規則。這使得程序不得不使用人工內存管理的老技術。幸運的是,從 JDK 1.2 開始,垃圾收集器提供了一種聲明這種對象生命周期依賴性的方法,這樣垃圾收集器就可以幫助我們防止這種內存泄漏 —— 利用弱引用

弱引用是對一個對象(稱為 referent)的引用的持有者。使用弱引用后,可以維持對 referent 的引用,而不會阻止它被垃圾收集。當垃圾收集器跟蹤堆的時候,如果對一個對象的引用只有弱引用,那么這個 referent 就會成為垃圾收集的候選對象,就像沒有任何剩余的引用一樣,而且所有剩余的弱引用都被清除。(只有弱引用的對象稱為弱可及(weakly reachable。)

WeakReference referent 是在構造時設置的,在沒有被清除之前,可以用 get() 獲取它的值。如果弱引用被清除了(不管是 referent 已經被垃圾收集了,還是有人調用了 WeakReference.clear()),get() 會返回 null。相應地,在使用其結果之前,應當總是檢查 get() 是否返回一個非 null 值,因為 referent 最終總是會被垃圾收集的。

用一個普通的(強)引用拷貝一個對象引用時,限制 referent 的生命周期至少與被拷貝的引用的生命周期一樣長。如果不小心,那么它可能就與程序的生命周期一樣 —— 如果將一個對象放入一個全局集合中的話。另一方面,在創建對一個對象的弱引用時,完全沒有擴展 referent 的生命周期,只是在對象仍然存活的時候,保持另一種到達它的方法。

弱引用對于構造弱集合最有用,如那些在應用程序的其余部分使用對象期間存儲關于這些對象的元數據的集合 —— 這就是 SocketManager 類所要做的工作。因為這是弱引用最常見的用法,WeakHashMap 也被添加到 JDK 1.2 的類庫中,它對鍵(而不是對值)使用弱引用。如果在一個普通 HashMap 中用一個對象作為鍵,那么這個對象在映射從 Map 中刪除之前不能被回收,WeakHashMap 使您可以用一個對象作為 Map 鍵,同時不會阻止這個對象被垃圾收集。清單 5 給出了 WeakHashMap get() 方法的一種可能實現,它展示了弱引用的使用:


清單 5. WeakReference.get() 的一種可能實現

 
public class WeakHashMap<K,V> implements Map<K,V> {
    private static class Entry<K,V> extends WeakReference<K> 
      implements Map.Entry<K,V> {
        private V value;
        private final int hash;
        private Entry<K,V> next;
        ...
    }
    public V get(Object key) {
        int hash = getHash(key);
        Entry<K,V> e = getChain(hash);
        while (e != null) {
            K eKey= e.get();
            if (e.hash == hash && (key == eKey || key.equals(eKey)))
                return e.value;
            e = e.next;
        }
        return null;
    }

 

調用 WeakReference.get() 時,它返回一個對 referent 的強引用(如果它仍然存活的話),因此不需要擔心映射在 while 循環體中消失,因為強引用會防止它被垃圾收集。WeakHashMap 的實現展示了弱引用的一種常見用法 —— 一些內部對象擴展 WeakReference。其原因在下面一節討論引用隊列時會得到解釋。

在向 WeakHashMap 中添加映射時,請記住映射可能會在以后脫離,因為鍵被垃圾收集了。在這種情況下,get() 返回 null,這使得測試 get() 的返回值是否為 null 變得比平時更重要了。

WeakHashMap 堵住泄漏

SocketManager 中防止泄漏很容易,只要用 WeakHashMap 代替 HashMap 就行了,如清單 6 所示。(如果 SocketManager 需要線程安全,那么可以用 Collections.synchronizedMap() 包裝 WeakHashMap)。當映射的生命周期必須與鍵的生命周期聯系在一起時,可以使用這種方法。不過,應當小心不濫用這種技術,大多數時候還是應當使用普通的 HashMap 作為 Map 的實現。


清單 6. WeakHashMap 修復 SocketManager

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

 

引用隊列

WeakHashMap 用弱引用承載映射鍵,這使得應用程序不再使用鍵對象時它們可以被垃圾收集,get() 實現可以根據 WeakReference.get() 是否返回 null 來區分死的映射和活的映射。但是這只是防止 Map 的內存消耗在應用程序的生命周期中不斷增加所需要做的工作的一半,還需要做一些工作以便在鍵對象被收集后從 Map 中刪除死項。否則,Map 會充滿對應于死鍵的項。雖然這對于應用程序是不可見的,但是它仍然會造成應用程序耗盡內存,因為即使鍵被收集了,Map.Entry 和值對象也不會被收集。

可以通過周期性地掃描 Map,對每一個弱引用調用 get(),并在 get() 返回 null 時刪除那個映射而消除死映射。但是如果 Map 有許多活的項,那么這種方法的效率很低。如果有一種方法可以在弱引用的 referent 被垃圾收集時發出通知就好了,這就是引用隊列 的作用。

引用隊列是垃圾收集器向應用程序返回關于對象生命周期的信息的主要方法。弱引用有兩個構造函數:一個只取 referent 作為參數,另一個還取引用隊列作為參數。如果用關聯的引用隊列創建弱引用,在 referent 成為 GC 候選對象時,這個引用對象(不是 referent)就在引用清除后加入 到引用隊列中。之后,應用程序從引用隊列提取引用并了解到它的 referent 已被收集,因此可以進行相應的清理活動,如去掉已不在弱集合中的對象的項。(引用隊列提供了與 BlockingQueue 同樣的出列模式 —— polledtimed blocking untimed blocking。)

WeakHashMap 有一個名為 expungeStaleEntries() 的私有方法,大多數 Map 操作中會調用它,它去掉引用隊列中所有失效的引用,并刪除關聯的映射。清單 7 展示了 expungeStaleEntries() 的一種可能實現。用于存儲鍵-值映射的 Entry 類型擴展了 WeakReference,因此當 expungeStaleEntries() 要求下一個失效的弱引用時,它得到一個 Entry。用引用隊列代替定期掃描內容的方法來清理 Map 更有效,因為清理過程不會觸及活的項,只有在有實際加入隊列的引用時它才工作。


清單 7. WeakHashMap.expungeStaleEntries() 的可能實現

 
    private void expungeStaleEntries() {
        Entry<K,V> e;
        while ( (e = (Entry<K,V>) queue.poll()) != null) {
            int hash = e.hash;
            Entry<K,V> prev = getChain(hash);
            Entry<K,V> cur = prev;
            while (cur != null) {
                Entry<K,V> next = cur.next;
                if (cur == e) {
                    if (prev == e)
                        setChain(hash, next);
                    else
                        prev.next = next;
                    break;
                }
                prev = cur;
                cur = next;
            }
        }
    }

 

clip_image002
clip_image004

clip_image005

 

結束語

弱引用和弱集合是對堆進行管理的強大工具,使得應用程序可以使用更復雜的可及性方案,而不只是由普通(強)引用所提供的要么全部要么沒有可及性。下個月,我們將分析與弱引用有關的軟引用,將分析在使用弱引用和軟引用時,垃圾收集器的行為。

 

posted on 2009-06-15 13:07 肥仔 閱讀(325) 評論(0)  編輯 收藏 引用 所屬分類: Web-后臺

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲精品免费一区二区三区| 亚洲欧美日韩国产综合在线| 嫩草影视亚洲| 亚洲第一黄色网| 亚洲天堂av高清| 国产九色精品成人porny| 久久久av网站| 亚洲人成人77777线观看| 亚洲国产第一| 在线午夜精品| 亚洲国产成人av好男人在线观看| 欧美激情欧美狂野欧美精品| 午夜一区二区三区在线观看| 在线日韩一区二区| 国产欧美精品va在线观看| 久久日韩粉嫩一区二区三区| 亚洲一区二区三区在线观看视频| 亚洲黄色影院| 国产精品人成在线观看免费| 欧美成人精品激情在线观看| 性色av一区二区三区在线观看| 韩日精品在线| 欧美日韩在线观看一区二区三区 | 欧美成人免费大片| 国产精品久久久爽爽爽麻豆色哟哟| 久久久久久伊人| 亚洲自拍另类| 亚洲一区二区三区在线视频| 久久精品国产99国产精品| 亚洲欧美日韩精品久久久| 久久免费偷拍视频| 国产精品三上| 亚洲乱码国产乱码精品精天堂 | 欧美日韩在线第一页| 国产在线一区二区三区四区| 国产亚洲欧美一区| 国产在线拍揄自揄视频不卡99| 亚洲精品一区在线观看| 久久综合久色欧美综合狠狠 | 一区二区三区蜜桃网| 亚洲国产日韩在线| 另类图片国产| 亚洲天堂免费观看| 亚洲一区二区三区在线播放| 免费成人av资源网| 欧美电影免费观看高清| 国产午夜精品理论片a级大结局| 国产毛片久久| 亚洲夜间福利| 久久精品欧美日韩| 久久久久久网址| 欧美成年人视频网站欧美| 亚洲黄色有码视频| 六月婷婷久久| 亚洲成人在线视频播放| 在线亚洲自拍| 六月天综合网| 91久久黄色| 免费日韩一区二区| 国产一区在线免费观看| 欧美主播一区二区三区| 亚洲午夜激情网页| 欧美岛国激情| 91久久久在线| 欧美人牲a欧美精品| 欧美日韩综合另类| 国产日韩成人精品| 午夜久久影院| 亚洲激情一区二区三区| 欧美11—12娇小xxxx| 亚洲第一在线综合网站| 欧美肥婆bbw| 国产情侣久久| 欧美国产一区二区在线观看| 欧美国产另类| 99天天综合性| 猛干欧美女孩| 亚洲视频在线二区| 国产精品一级久久久| 久久久久99| 亚洲图片在线| 国产欧美精品日韩精品| 久久频这里精品99香蕉| 美女脱光内衣内裤视频久久网站| 亚洲日本一区二区| 久久久久久综合| 久久一区视频| 在线视频一区观看| 午夜影院日韩| 亚洲精品小视频在线观看| 亚洲午夜视频在线观看| 精品成人在线| 久久久精品免费视频| 久久女同精品一区二区| 中日韩视频在线观看| 欧美在线观看一二区| 亚洲免费电影在线| 性色av一区二区三区在线观看| 亚洲国内自拍| 欧美96在线丨欧| 欧美特黄a级高清免费大片a级| 久久久一区二区三区| 欧美日韩国产在线一区| 99国产精品国产精品久久 | 久久久一二三| 欧美精品黄色| 一区二区激情视频| 久久精品日韩欧美| 亚洲综合欧美| 亚洲欧美经典视频| 亚洲精品免费在线播放| 新67194成人永久网站| 一区二区三区黄色| 麻豆91精品91久久久的内涵| 香蕉久久一区二区不卡无毒影院| 欧美大片专区| 你懂的国产精品| 狠狠色噜噜狠狠狠狠色吗综合| 亚洲图片你懂的| 亚洲网站在线播放| 欧美不卡一卡二卡免费版| 亚洲精品乱码久久久久| 在线观看久久av| 免费欧美日韩国产三级电影| 国产精品成人免费精品自在线观看| 一区二区三区国产盗摄| 免费不卡中文字幕视频| 美女在线一区二区| 国产夜色精品一区二区av| 亚洲欧美国产三级| 欧美一区二区日韩| 久久亚洲综合色一区二区三区| 久久av在线看| 免费91麻豆精品国产自产在线观看| 久久久久久久综合色一本| 国产视频欧美| 久久国产精品亚洲va麻豆| 久久精品一区| 禁久久精品乱码| 美女啪啪无遮挡免费久久网站| 亚洲电影免费在线| 99国内精品| 国产精品久久久久9999高清 | 一区二区免费看| 亚洲欧美中文日韩在线| 国产精品日韩在线播放| 小处雏高清一区二区三区| 久久婷婷综合激情| 亚洲欧洲在线视频| 亚洲欧美日韩视频一区| 欧美伊人精品成人久久综合97| 国产一区二区精品| 久久免费视频在线观看| 最新国产乱人伦偷精品免费网站| 一区二区福利| 国产日韩欧美在线一区| 久久天天躁狠狠躁夜夜爽蜜月| 亚洲国产成人精品久久| 亚洲无亚洲人成网站77777 | 亚洲女优在线| 欧美精品一区二区三区久久久竹菊| 亚洲国产日韩欧美| 亚洲永久免费| 激情欧美一区二区| 欧美日韩高清不卡| 欧美一区二区视频免费观看| 亚洲一区免费网站| 国产日韩欧美高清免费| 嫩模写真一区二区三区三州| 亚洲自拍都市欧美小说| 久久久女女女女999久久| 99综合在线| 黑人巨大精品欧美一区二区| 欧美日韩日日骚| 久久久久在线观看| 中文欧美字幕免费| 欧美成年视频| 欧美在线啊v| 一本色道久久综合亚洲精品不| 国产一二三精品| 欧美性一区二区| 亚洲午夜精品久久久久久浪潮| 久久久国产成人精品| 亚洲午夜未删减在线观看| 极品少妇一区二区| 国产精品稀缺呦系列在线| 欧美高清在线| 久久中文字幕导航| 亚洲一区二区三区中文字幕| 久久久欧美精品sm网站| 亚洲深夜激情| 亚洲片区在线| 狠狠干成人综合网| 国产精品日韩电影| 欧美日韩一级黄| 欧美激情综合五月色丁香小说| 久久激情视频久久| 亚洲欧洲av一区二区三区久久| 日韩亚洲精品在线| 午夜精品视频在线观看|