怎樣用Java編寫一段代碼引起內存泄露

經過下面步驟可以很是easy產生內存泄露(程序代碼不能訪問到某些對象,但是它們仍然保存在內存中):java

  1. 應用程序建立一個長時間執行的線程(或者使用線程池,會更快地發生內存泄露)。
  2. 線程經過某個類載入器(可以本身定義)載入一個類。
  3. 該類分配了大塊內存(比方new byte[1000000]),在某個靜態變量存儲一個強引用,而後在ThreadLocal中存儲它自身的引用。分配額外的內存new byte[1000000]是可選的(類實例泄露已經足夠了),但是這樣會使內存泄露更快。
  4. 線程清理本身定義的類或者載入該類的類載入器。
  5. 反覆以上步驟。

由於沒有了對類和類載入器的引用,ThreadLocal中的存儲就不能被訪問到。ThreadLocal持有該對象的引用,它也就持有了這個類及其類載入器的引用,類載入器持有它所載入的類的所有引用,這樣GC沒法回收ThreadLocal中存儲的內存。在很是多JVM的實現中Java類和類載入器直接分配到permgen區域不運行GC,這樣致使了更嚴重的內存泄露。linux

這樣的泄露模式的變種之中的一個就是假設你經常又一次部署以不論什麼形式使用了ThreadLocal的應用程序、應用容器(比方Tomcat)會很是easy發生內存泄露(由於應用容器使用瞭如前所述的線程,每次又一次部署應用時將使用新的類載入器)。web

A2:緩存

靜態變量引用對象安全

1 class MemorableClass {
2     static final ArrayList list = new ArrayList(100);
3 }

調用長字符串的String.intern()網絡

1 String str=readString(); // read lengthy string any source db,textbox/jsp etc..
2 // This will place the string in memory pool from which you cant remove
3 str.intern();

未關閉已打開流(文件,網絡等)session

1 try {
2     BufferedReader br = new BufferedReader(new FileReader(inputFile));
3     ...
4     ...
5 } catch (Exception e) {
6     e.printStacktrace();
7 }

未關閉鏈接app

1 try {
2     Connection conn = ConnectionFactory.getConnection();
3     ...
4     ...
5 } catch (Exception e) {
6     e.printStacktrace();
7 }

JVM的GC不可達區域框架

比方經過native方法分配的內存。jsp

web應用在application範圍的對象,應用未從新啓動或者沒有顯式移除

getServletContext().setAttribute("SOME_MAP", map);

web應用在session範圍的對象,未失效或者沒有顯式移除

session.setAttribute("SOME_MAP", map);

不對或者不合適的JVM選項

比方IBM JDK的noclassgc阻止了無用類的垃圾回收

A3:假設HashSet未正確實現(或者未實現)hashCode()或者equals(),會致使集合中持續添加�「副本」。假設集合不能地忽略掉它應該忽略的元素,它的大小就僅僅能持續增加,而且不能刪除這些元素。

假設你想要生成錯誤的鍵值對,可以像如下這樣作:

1 class BadKey {
2    // no hashCode or equals();
3    public final String key;
4    public BadKey(String key) { this.key = key; }
5 }
6  
7 Map map = System.getProperties();
8 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()。
相關文章
相關標籤/搜索