譯序:Java 的內存洩漏,這不是一個新話題。Jim Patrick 的這篇文章早在 2001 年就寫出來了。但這並不意味著 Java 的內存洩漏是一個過時了的甚至不重要的話題。相反,Java 的內存洩漏應當是每一個關心程序健壯性、穩定性和高性能的程序員所必須了解的知識。
本文將揭示什麼時候需要關注內存洩漏以及如何進行防止。
摘要:Java 程序裡也存在內存洩漏?當然。和流行的看法相反,內存管理仍然是 Java 編程時應該考慮的事情。在這篇文章裡,你會了解到是什麼原因導致了 Java 內存洩漏以及什麼時候需要對這些洩漏進行關注。你也將會學到一個快速實用的課程以應對自己項目中的內存洩漏。
Java 程序裡的內存洩漏是如何表現的
大多數程序員都知道使用類似於 Java 的編程語言的好處之一就是他們無需再為內存的分配和釋放所擔心了。你只需要簡單地創建對象,當它們不再為程序所需要時 Java 會自行通過一個被稱為垃圾收集的機制將其移除。這個過程意味著 Java 已經解決了困擾其他編程語言的一個棘手的問題 -- 可怕的內存洩漏。果真是這樣的嗎?
在進行深入討論之前,讓我們先回顧一下垃圾收集是如何進行實際工作的。垃圾收集器的工作就是找到程序不再需要的對象並在當它們不再被訪問或引用時將它們移除掉。垃圾收集器從貫穿整個程序生命周期的類這個根節點開始,掃描所有引用到的節點。在遍歷節點時,它跟蹤那些被活躍引用著的對象。那些不再被引用的對象就滿足了垃圾回收的條件。當這些對象被移除時被它們占用的內存資源會交還給 Java 虛擬機(JVM)。
因此 Java 代碼的確不需要程序員負責內存管理的清理工作,它自行對不再使用的對象進行垃圾收集。然而,需要記住的是,垃圾收集的關鍵在於一個對象在不再被引用時才被統計為不再使用。下圖對這一概念進行了說明。
上圖表示在一個 Java 程序執行時具有不同的生命周期的兩個類。類 A 首先被實例化,它存在的時間比較長,幾乎貫穿整個進程的生命周期。在某個時間點,類 B 被創建,類 A 添加了一個對這個新建類的引用。我們假設類 B 是某個用於顯示並返回用戶指令的用戶界面部件。盡管類 B 不再被使用,如果類 A 對類 B 的引用未被清除,類 B 將繼續存在並占據內存空間,即使下一次垃圾收集被執行。
什麼時候需要注意內存洩漏?
如果在你的程序執行一段時間之後遇到 java.lang.OutOfMemoryError 的話,內存洩漏無疑是最值得懷疑的。除了這種明顯的情況之外,什麼時候需要考慮內存洩漏?完美主義的程序員會回答說所有的內存洩漏都需要進行審查和更改。然而,在跳到這一結論之前還需要考慮其他幾點因素,包括程序的生命周期以及內存洩漏的大小。
考慮一下在一個程序的生命周期裡垃圾收集器可能從未執行的情況。無法保證什麼時候 JVM 會調用垃圾收集 -- 即使程序顯式調用 System.gc()。通常情況下,垃圾收集器不會自動運行,直到程序需要比目前可用內存還要多的內存。此時,JVM 會首先嘗試調用垃圾收集器以獲取更多可用內存。如果這個嘗試仍舊不能夠釋放出足夠的資源,JVM 將會從操作系統獲取更多內存,直到達到所允許內存的最大值。
舉個例子來說,一個小型的 Java 應用程序,用來顯示一些簡單的配置修改的用戶界面元素,出現了內存洩漏。垃圾收集器可能在程序關閉之前都不會被調用到,因為 JVM 可能總是有足夠的內存來創建程序所需要的所有對象。因此,在這種情況下,即便是一些已死對象在程序運行的時候仍舊占據著內存,但這並不影響實際應用。
如果開發中的 Java 代碼將以每天 24 小時運行在服務器上,這時內存洩漏將會比上面的那個配置工具程序要明顯的多了。即便是代碼中最小的內存洩漏,在持續運行的情況下最終也將耗盡所有可用內存。
相反的情況下,即使一個程序只是短暫存活,卻分配了大量臨時對象(或者少量的占用大量內存的對象),在這些對象不再需要時沒有取消引用,這樣的 Java 代碼也會達到內存限制。
最後一個值得注意的問題是,不必過於擔心(Java 程序所造成的)內存洩漏。Java 內存洩漏不應該被認為是像其他語言中所發生的那樣危險,比如 C++ 的內存丟失將永遠不會返回給操作系統。Java 應用程序中,我們把不再需要的卻占據著內存資源的對象都交給 JVM。所以在理論上來說,一旦 Java 程序和它的 JVM 關閉掉,所有分配的內存都將歸還給操作系統。