溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中怎么用弱引用堵住內(nèi)存泄漏

發(fā)布時(shí)間:2021-12-21 14:48:45 來(lái)源:億速云 閱讀:106 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Java中怎么用弱引用堵住內(nèi)存泄漏”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java中怎么用弱引用堵住內(nèi)存泄漏”吧!

雖然用 Java? 語(yǔ)言編寫(xiě)的程序在理論上是不會(huì)出現(xiàn)“內(nèi)存泄漏”的,但是有時(shí)對(duì)象在不再作為程序的邏輯狀態(tài)的一部分之后仍然不被垃圾收集。本月,負(fù)責(zé)保障應(yīng)用程序健康的工程師 Brian Goetz 探討了無(wú)意識(shí)的對(duì)象保留的常見(jiàn)原因,并展示了如何用弱引用堵住泄漏。
  要讓垃圾收集(GC)回收程序不再使用的對(duì)象,對(duì)象的邏輯 生命周期(應(yīng)用程序使用它的時(shí)間)和對(duì)該對(duì)象擁有的引用的實(shí)際 生命周期必須是相同的。在大多數(shù)時(shí)候,好的軟件工程技術(shù)保證這是自動(dòng)實(shí)現(xiàn)的,不用我們對(duì)對(duì)象生命周期問(wèn)題花費(fèi)過(guò)多心思。但是偶爾我們會(huì)創(chuàng)建一個(gè)引用,它在內(nèi)存中包含對(duì)象的時(shí)間比我們預(yù)期的要長(zhǎng)得多,這種情況稱為無(wú)意識(shí)的對(duì)象保留(unintentional object retention)。

  全局 Map 造成的內(nèi)存泄漏

  無(wú)意識(shí)對(duì)象保留最常見(jiàn)的原因是使用 Map 將元數(shù)據(jù)與臨時(shí)對(duì)象(transient object)相關(guān)聯(lián)。假定一個(gè)對(duì)象具有中等生命周期,比分配它的那個(gè)方法調(diào)用的生命周期長(zhǎng),但是比應(yīng)用程序的生命周期短,如客戶機(jī)的套接字連接。需要將一些元數(shù)據(jù)與這個(gè)套接字關(guān)聯(lián),如生成連接的用戶的標(biāo)識(shí)。在創(chuàng)建 Socket 時(shí)是不知道這些信息的,并且不能將數(shù)據(jù)添加到 Socket 對(duì)象上,因?yàn)椴荒芸刂?Socket 類(lèi)或者它的子類(lèi)。這時(shí),典型的方法就是在一個(gè)全局 Map 中存儲(chǔ)這些信息,如清單 1 中的 SocketManager 類(lèi)所示:

  清單 1. 使用一個(gè)全局 Map 將元數(shù)據(jù)關(guān)聯(lián)到一個(gè)對(duì)象

  public class SocketManager {
   private Map

m = new HashMap();
  
   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);

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

  找出內(nèi)存泄漏

  程序有內(nèi)存泄漏的第一個(gè)跡象通常是它拋出一個(gè) OutOfMemoryError,或者因?yàn)轭l繁的垃圾收集而表現(xiàn)出糟糕的性能。幸運(yùn)的是,垃圾收集可以提供能夠用來(lái)診斷內(nèi)存泄漏的大量信息。如果以 -verbose:gc 或者 -Xloggc 選項(xiàng)調(diào)用 JVM,那么每次 GC 運(yùn)行時(shí)在控制臺(tái)上或者日志文件中會(huì)打印出一個(gè)診斷信息,包括它所花費(fèi)的時(shí)間、當(dāng)前堆使用情況以及恢復(fù)了多少內(nèi)存。記錄 GC 使用情況并不具有干擾性,因此如果需要分析內(nèi)存問(wèn)題或者調(diào)優(yōu)垃圾收集器,在生產(chǎn)環(huán)境中默認(rèn)啟用 GC 日志是值得的。

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

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

  清單 2. 具有基于 Map 的內(nèi)存泄漏的程序

  public class MapLeaker {
   public ExecutorService exec = Executors.newFixedThreadPool(5);
   public MaptaskStatus
   = Collections.synchronizedMap(new HashMap());
   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;
   }
  }
Java中怎么用弱引用堵住內(nèi)存泄漏
  確信有了內(nèi)存泄漏后,下一步就是找出哪種對(duì)象造成了這個(gè)問(wèn)題。所有內(nèi)存分析器都可以生成按照對(duì)象類(lèi)進(jìn)行分解的堆快照。有一些很好的商業(yè)堆分析工具,但是找出內(nèi)存泄漏不一定要花錢(qián)買(mǎi)這些工具 —— 內(nèi)置的 hprof 工具也可完成這項(xiàng)工作。要使用 hprof 并讓它跟蹤內(nèi)存使用,需要以 -Xrunhprof:heap=sites 選項(xiàng)調(diào)用 JVM。

到此,相信大家對(duì)“Java中怎么用弱引用堵住內(nèi)存泄漏”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI