如何用Java編寫一段程式碼引發記憶體洩露
本文來自StackOverflow問答網站的一個熱門討論:如何用Java編寫一段會發生記憶體洩露的程式碼。
Q:剛才我參加了面試,面試官問我如何寫出會發生記憶體洩露的Java程式碼。這個問題我一點思路都沒有,好囧。
A1:通過以下步驟可以很容易產生記憶體洩露(程式程式碼不能訪問到某些物件,但是它們仍然儲存在記憶體中):
- 應用程式建立一個長時間執行的執行緒(或者使用執行緒池,會更快地發生記憶體洩露)。
- 執行緒通過某個類載入器(可以自定義)載入一個類。
- 該類分配了大塊記憶體(比如
new byte[1000000]
),在某個靜態變數儲存一個強引用,然後在ThreadLocal中儲存它自身的引用。分配額外的記憶體new byte[1000000]
是可選的(類例項洩露已經足夠了),但是這樣會使記憶體洩露更快。 - 執行緒清理自定義的類或者載入該類的類載入器。
- 重複以上步驟。
由於沒有了對類和類載入器的引用,ThreadLocal中的儲存就不能被訪問到。ThreadLocal持有該物件的引用,它也就持有了這個類及其類載入器的引用,類載入器持有它所載入的類的所有引用,這樣GC無法回收ThreadLocal中儲存的記憶體。在很多JVM的實現中Java類和類載入器直接分配到permgen區域不執行GC,這樣導致了更嚴重的記憶體洩露。
這種洩露模式的變種之一就是如果你經常重新部署以任何形式使用了ThreadLocal的應用程式、應用容器(比如Tomcat)會很容易發生記憶體洩露(由於應用容器使用瞭如前所述的執行緒,每次重新部署應用時將使用新的類載入器)。
A2:
靜態變數引用物件
class MemorableClass { static final ArrayList list = new ArrayList(100); }
呼叫長字串的String.intern()
String str=readString(); // read lengthy string any source db,textbox/jsp etc.. // This will place the string in memory pool from which you cant remove str.intern();
未關閉已開啟流(檔案,網路等)
try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); }
未關閉連線
try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); }
JVM的GC不可達區域
比如通過native方法分配的記憶體。
web應用在application範圍的物件,應用未重啟或者沒有顯式移除
getServletContext().setAttribute("SOME_MAP", map);
web應用在session範圍的物件,未失效或者沒有顯式移除
session.setAttribute("SOME_MAP", map);
不正確或者不合適的JVM選項
比如IBM JDK的noclassgc阻止了無用類的垃圾回收
A3:如果HashSet未正確實現(或者未實現)hashCode()
或者equals()
,會導致集合中持續增加“副本”。如果集合不能地忽略掉它應該忽略的元素,它的大小就只能持續增長,而且不能刪除這些元素。
如果你想要生成錯誤的鍵值對,可以像下面這樣做:
class BadKey { // no hashCode or equals(); public final String key; public BadKey(String key) { this.key = key; } } Map map = System.getProperties(); map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
A4:除了被遺忘的監聽器,靜態引用,hashmap中key錯誤/被修改或者執行緒阻塞不能結束生命週期等典型記憶體洩露場景,下面介紹一些不太明顯的Java發生記憶體洩露的情況,主要是執行緒相關的。
Runtime.addShutdownHook
後沒有移除,即使使用了removeShutdownHook,由於ThreadGroup類對於未啟動執行緒的bug,它可能不被回收,導致ThreadGroup發生記憶體洩露。- 建立但未啟動執行緒,與上面的情形相同
- 建立繼承了
ContextClassLoader
和AccessControlContext
的執行緒,ThreadGroup
和InheritedThreadLocal
的使用,所有這些引用都是潛在的洩露,以及所有被類載入器載入的類和所有靜態引用等等。這對ThreadFactory
介面作為重要組成元素整個j.u.c.Executor框架(java.util.concurrent)的影響非常明顯,很多開發人員沒有注意到它潛在的危險。而且很多庫都會按照請求啟動執行緒。 ThreadLocal
快取,很多情況下不是好的做法。有很多基於ThreadLocal的簡單快取的實現,但是如果執行緒在它的期望生命週期外繼續執行ContextClassLoader將發生洩露。除非真正必要不要使用ThreadLocal快取。- 當ThreadGroup自身沒有執行緒但是仍然有子執行緒組時呼叫
ThreadGroup.destroy()
。發生記憶體洩露將導致該執行緒組不能從它的父執行緒組移除,不能列舉子執行緒組。 - 使用WeakHashMap,value直接(間接)引用key,這是個很難發現的情形。這也適用於繼承
Weak/SoftReference
的類可能持有對被保護物件的強引用。 - 使用http(s)協議的
java.net.URL
下載資源。KeepAliveCache
在系統ThreadGroup建立新執行緒,導致當前執行緒的上下文類載入器記憶體洩露。沒有存活執行緒時執行緒在第一次請求時建立,所以很有可能發生洩露。(在Java7中已經修正了,建立執行緒的程式碼合理地移除了上下文類載入器。) - 使用
InflaterInputStream
在建構函式(比如PNGImageDecoder
)中傳遞new java.util.zip.Inflater()
,不呼叫inflater的end()
。僅僅是new的話非常安全,但如果自己建立該類作為建構函式引數時呼叫流的close()
不能關閉inflater,可能發生記憶體洩露。這並不是真正的記憶體洩露因為它會被finalizer釋放。但這消耗了很多native記憶體,導致linux的oom_killer殺掉程式。所以這給我們的教訓是:儘可能早地釋放native資源。 java.util.zip.Deflater
也一樣,它的情況更加嚴重。好的地方可能是很少用到Deflater
。如果自己建立了Deflater
或者Inflater
記住必須呼叫end()
。
相關文章
- Pprof定位Go程式記憶體洩露Go記憶體洩露
- SHBrowseForFolder 記憶體洩露記憶體洩露
- java中如何檢視記憶體洩露Java記憶體洩露
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- 從記憶體洩露、記憶體溢位和堆外記憶體,JVM優化引數配置引數記憶體洩露記憶體溢位JVM優化
- Lowmemorykiller記憶體洩露分析記憶體洩露
- JAVA記憶體洩露的原因及解決Java記憶體洩露
- C程式記憶體洩露檢測工具——ValgrindC程式記憶體洩露
- 使用 mtrace 分析 “記憶體洩露”記憶體洩露
- 實戰Go記憶體洩露Go記憶體洩露
- Android 記憶體洩露詳解Android記憶體洩露
- ThreadLocal原始碼解讀和記憶體洩露分析thread原始碼記憶體洩露
- Linux記憶體洩露案例分析和記憶體管理分享Linux記憶體洩露
- ArkTS 的記憶體快照與記憶體洩露除錯記憶體洩露除錯
- nodejs爬蟲記憶體洩露排查NodeJS爬蟲記憶體洩露
- ThreadLocal原始碼解析,記憶體洩露以及傳遞性thread原始碼記憶體洩露
- 一行程式碼教你解決FlutterPlatformViews記憶體洩露(memory leak)行程FlutterPlatformView記憶體洩露
- win10驅動記憶體洩露如何解決_win10記憶體洩露處理方法Win10記憶體洩露
- Java記憶體洩漏Java記憶體
- android Handler導致的記憶體洩露Android記憶體洩露
- netty 堆外記憶體洩露排查盛宴Netty記憶體洩露
- 乾貨分享:淺談記憶體洩露記憶體洩露
- 解決git記憶體洩露問題Git記憶體洩露
- Spring Boot heapdump洩露記憶體分析方法Spring Boot記憶體
- 線上記憶體洩露定位--memleak工具記憶體洩露
- 記一次尷尬的Java應用記憶體洩露排查Java記憶體洩露
- php常駐程式記憶體洩露的簡單解決PHP記憶體洩露
- Java應用程式中的記憶體洩漏及記憶體管理Java記憶體
- 記一次"記憶體洩露"排查過程記憶體洩露
- Java面試題:細數ThreadLocal大坑,記憶體洩露本可避免Java面試題thread記憶體洩露
- Bulk 異常引發的 Elasticsearch 記憶體洩漏Elasticsearch記憶體
- 簡單的記憶體“洩露”和“溢位”記憶體
- 一個 Vue 頁面的記憶體洩露分析Vue記憶體洩露
- 一個Vue頁面的記憶體洩露分析Vue記憶體洩露
- Android效能最佳化之記憶體洩露Android記憶體洩露
- Python實現記憶體洩露排查的示例Python記憶體洩露
- 小題大做 | Handler記憶體洩露全面分析記憶體洩露
- 記一次 .NET 某工控軟體 記憶體洩露分析記憶體洩露
- 納尼,Java 存在記憶體洩洩洩洩洩洩漏嗎?Java記憶體