<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 9, comments - 4, trackbacks - 0, articles - 21

    用弱引用堵住Java內存泄漏- -

    Posted on 2007-10-19 11:14 一步一步努力向上爬 閱讀(691) 評論(0)  編輯  收藏 所屬分類: JAVA優化
    ava 理論與實踐: 用弱引用堵住內存泄漏

    http://www-128.ibm.com/developerworks/cn/java/j-jtp11225/index.html?ca=dwcn-newsletter-java

    級別: 中級

    Brian Goetz , 首席顧問, Quiotix

    2005 年 12 月 19 日

    雖 然用 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 中刪除相應的映射,否則,SocketUser 對象將會永遠留在 Map 中,遠遠超過響應了請求和關閉套接字的時間。這會阻止 SocketUser 對象被垃圾收集,即使應用程序不會再使用它們。這些對象留下來不受控制,很容易造成程序在長時間運行后內存爆滿。除了最簡單的情況,在幾乎所有情況下找出什么時候 Socket 不再被程序使用是一件很煩人和容易出錯的任務,需要人工對內存進行管理。



    回頁首


    找出內存泄漏

    程序有內存泄漏的第一個跡象通常是它拋出一個 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. 持續上升的內存使用趨勢

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

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

    請參閱 清單 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)

    主站蜘蛛池模板: 亚洲av日韩aⅴ无码色老头| 亚洲欧洲国产综合| 免费夜色污私人影院网站| 在线观看免费亚洲| 亚洲AV第一成肉网| 无码欧精品亚洲日韩一区夜夜嗨| 亚洲av无码片vr一区二区三区| 在线免费观看一级毛片| 亚洲AV永久无码精品网站在线观看 | 免费在线观看视频网站| 亚洲人成电影在线天堂| 99精品视频在线观看免费播放| 久久久久亚洲AV片无码下载蜜桃| 在免费jizzjizz在线播| 国产精品亚洲综合五月天| 在线观看人成视频免费| 国产亚洲精品精品精品| 国产亚洲成人久久| 日韩免费在线观看视频| 亚洲国产成人久久| 国产老女人精品免费视频| 一个人看的免费视频www在线高清动漫 | 最新黄色免费网站| 亚洲中文字幕AV每天更新| 国产男女猛烈无遮挡免费视频网站 | 狠狠色香婷婷久久亚洲精品| 18禁超污无遮挡无码免费网站国产| 中文字幕亚洲精品无码| 亚洲福利视频一区二区| 国产一区二区免费| 亚洲一级毛片在线播放| 青青青青青青久久久免费观看| fc2成年免费共享视频网站| 亚洲国产精品不卡在线电影| 噼里啪啦免费观看高清动漫4| 国产亚洲精品仙踪林在线播放| 亚洲精品卡2卡3卡4卡5卡区| 国产福利在线免费| h视频免费高清在线观看| 亚洲视频一区二区三区| 免费又黄又爽的视频|