溫馨提示×

溫馨提示×

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

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

如何用Java編寫一段代碼引發(fā)內(nèi)存泄露

發(fā)布時間:2021-10-29 18:22:41 來源:億速云 閱讀:242 作者:柒染 欄目:編程語言

今天就跟大家聊聊有關(guān)如何用Java編寫一段代碼引發(fā)內(nèi)存泄露,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

Q:剛才我參加了面試,面試官問我如何寫出會發(fā)生內(nèi)存泄露的Java代碼。這個問題我一點思路都沒有,好囧。

A1:通過以下步驟可以很容易產(chǎn)生內(nèi)存泄露(程序代碼不能訪問到某些對象,但是它們?nèi)匀槐4嬖趦?nèi)存中):

如何用Java編寫一段代碼引發(fā)內(nèi)存泄露

  1. 應(yīng)用程序創(chuàng)建一個長時間運行的線程(或者使用線程池,會更快地發(fā)生內(nèi)存泄露)。

  2. 線程通過某個類加載器(可以自定義)加載一個類。

  3. 該類分配了大塊內(nèi)存(比如new byte[1000000]),在某個靜態(tài)變量存儲一個強(qiáng)引用,然后在ThreadLocal中存儲它自身的引用。分配額外的內(nèi)存new byte[1000000]是可選的(類實例泄露已經(jīng)足夠了),但是這樣會使內(nèi)存泄露更快。

  4. 線程清理自定義的類或者加載該類的類加載器。

  5. 重復(fù)以上步驟。

由于沒有了對類和類加載器的引用,ThreadLocal中的存儲就不能被訪問到。ThreadLocal持有該對象的引用,它也就持有了這個類及 其類加載器的引用,類加載器持有它所加載的類的所有引用,這樣GC無法回收ThreadLocal中存儲的內(nèi)存。在很多JVM的實現(xiàn)中Java類和類加載 器直接分配到permgen區(qū)域不執(zhí)行GC,這樣導(dǎo)致了更嚴(yán)重的內(nèi)存泄露。

這種泄露模式的變種之一就是如果你經(jīng)常重新部署以任何形式使用了ThreadLocal的應(yīng)用程序、應(yīng)用容器(比如Tomcat)會很容易發(fā)生內(nèi)存泄露(由于應(yīng)用容器使用了如前所述的線程,每次重新部署應(yīng)用時將使用新的類加載器)。

A2:

靜態(tài)變量引用對象

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

調(diào)用長字符串的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();

未關(guān)閉已打開流(文件,網(wǎng)絡(luò)等)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

未關(guān)閉連接

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

JVM的GC不可達(dá)區(qū)域

比如通過native方法分配的內(nèi)存。

web應(yīng)用在application范圍的對象,應(yīng)用未重啟或者沒有顯式移除

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

web應(yīng)用在session范圍的對象,未失效或者沒有顯式移除

session.setAttribute("SOME_MAP", map);

不正確或者不合適的JVM選項

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

A3:如果HashSet未正確實現(xiàn)(或者未實現(xiàn))hashCode()或者equals(),會導(dǎo)致集合中持續(xù)增加“副本”。如果集合不能地忽略掉它應(yīng)該忽略的元素,它的大小就只能持續(xù)增長,而且不能刪除這些元素。

如果你想要生成錯誤的鍵值對,可以像下面這樣做:

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:除了被遺忘的監(jiān)聽器,靜態(tài)引用,hashmap中key錯誤/被修改或者線程阻塞不能結(jié)束生命周期等典型內(nèi)存泄露場景,下面介紹一些不太明顯的Java發(fā)生內(nèi)存泄露的情況,主要是線程相關(guān)的。

  • Runtime.addShutdownHook后沒有移除,即使使用了removeShutdownHook,由于ThreadGroup類對于未啟動線程的bug,它可能不被回收,導(dǎo)致ThreadGroup發(fā)生內(nèi)存泄露。

  • 創(chuàng)建但未啟動線程,與上面的情形相同

  • 創(chuàng)建繼承了ContextClassLoader和AccessControlContext的線程,ThreadGroup和InheritedThreadLocal的使用,所有這些引用都是潛在的泄露,以及所有被類加載器加載的類和所有靜態(tài)引用等等。這對ThreadFactory接口作為重要組成元素整個j.u.c.Executor框架(java.util.concurrent)的影響非常明顯,很多開發(fā)人員沒有注意到它潛在的危險。而且很多庫都會按照請求啟動線程。

  • ThreadLocal緩存,很多情況下不是好的做法。有很多基于ThreadLocal的簡單緩存的實現(xiàn),但是如果線程在它的期望生命周期外繼續(xù)運行ContextClassLoader將發(fā)生泄露。除非真正必要不要使用ThreadLocal緩存。

  • 當(dāng)ThreadGroup自身沒有線程但是仍然有子線程組時調(diào)用ThreadGroup.destroy()。發(fā)生內(nèi)存泄露將導(dǎo)致該線程組不能從它的父線程組移除,不能枚舉子線程組。

  • 使用WeakHashMap,value直接(間接)引用key,這是個很難發(fā)現(xiàn)的情形。這也適用于繼承Weak/SoftReference的類可能持有對被保護(hù)對象的強(qiáng)引用。

  • 使用http(s)協(xié)議的java.net.URL下載資源。KeepAliveCache在系統(tǒng)ThreadGroup創(chuàng)建新線程,導(dǎo)致當(dāng)前線程的上下文類加載器內(nèi)存泄露。沒有存活線程時線程在***次請求時創(chuàng)建,所以很有可能發(fā)生泄露。(在Java7中已經(jīng)修正了,創(chuàng)建線程的代碼合理地移除了上下文類加載器。)

  • 使用InflaterInputStream在構(gòu)造函數(shù)(比如PNGImageDecoder)中傳遞new java.util.zip.Inflater(),不調(diào)用inflater的end()。僅僅是new的話非常安全,但如果自己創(chuàng)建該類作為構(gòu)造函數(shù)參數(shù)時調(diào)用流的close()不能關(guān)閉inflater,可能發(fā)生內(nèi)存泄露。這并不是真正的內(nèi)存泄露因為它會被finalizer釋放。但這消耗了很多native內(nèi)存,導(dǎo)致linux的oom_killer殺掉進(jìn)程。所以這給我們的教訓(xùn)是:盡可能早地釋放native資源。

  • java.util.zip.Deflater也一樣,它的情況更加嚴(yán)重。好的地方可能是很少用到Deflater。如果自己創(chuàng)建了Deflater或者Inflater記住必須調(diào)用end()。

看完上述內(nèi)容,你們對如何用Java編寫一段代碼引發(fā)內(nèi)存泄露有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

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

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

AI