編寫Java代碼制造一個(gè)內(nèi)存溢出的情況
這將會是一篇比較邪惡的文章,當(dāng)你想在某個(gè)人的生活中制造悲劇時(shí)你可能會去google搜索它。在Java的世界里,內(nèi)存溢出僅僅只是你在這種情況下可能會引入的一種bug。你的受害者會在辦公室里度過幾天甚至是幾周的不眠之夜。
在這篇文章中我將會介紹兩種溢出方式,它們都是比較容易理解和重現(xiàn)的。并且它們都是來源現(xiàn)實(shí)項(xiàng)目的案例研究,但是為了讓你清晰地掌握,我把它們簡化了。
不過放心,在我們遇到和解決了很過溢出bug之后,類似的案例將會比你想象得更加普遍。
先來一個(gè)進(jìn)入狀態(tài)的,在使用HashSet/HashMap時(shí),所用鍵值沒有或者其equals()/hashCode()方法不正確,這會導(dǎo)致一個(gè)臭名昭著的錯(cuò)誤。
class KeylessEntry { static class Key { Integer id; Key(Integer id) { this.id = id; } @Override public int hashCode() { return id.hashCode(); } } public static void main(String[] args) { Map m = new HashMap(); while (true) for (int i = 0; i < 10000; i++) if (!m.containsKey(i)) m.put(new Key(i), "Number:" + i); } }
當(dāng)你運(yùn)行上面的代碼時(shí),你可能會期望它運(yùn)行起來永遠(yuǎn)不會出問題,畢竟內(nèi)置的緩存方案只會增加到10,000個(gè)元素,然后就不會再增加了,所有的key都已經(jīng)出現(xiàn)在 HashMap中。然而,事情并非如此。元素將會一直增長, 因?yàn)镵ey這個(gè)類沒有在hashCode()后實(shí)現(xiàn)一個(gè)合適的equals()方法。
解決方法很簡單,只要和下面的示例一樣添加一個(gè)equals方法就可以了。但是在找到問題所在之前,你肯定已經(jīng)花費(fèi)了不少寶貴的腦細(xì)胞。
@Override public boolean equals(Object o) { boolean response = false; if (o instanceof Key) { response = (((Key)o).id).equals(this.id); } return response; }
下一個(gè)你得提醒朋友的是和String處理相關(guān)的操作。它的表現(xiàn)會很詭異,特別是結(jié)合JVM版本差異的時(shí)候。String的內(nèi)部工作機(jī)制在 JDK 7u6中被改變了,所以如果你發(fā)現(xiàn)產(chǎn)品環(huán)境只是小版本號的區(qū)別,那么你已經(jīng)準(zhǔn)備好條件了。把類似下面的代碼給你的朋友調(diào)試,然后問他為什么這個(gè)bug只會在產(chǎn)品中出現(xiàn)。
class Stringer { static final int MB = 1024*512; static String createLongString(int length){ StringBuilder sb = new StringBuilder(length); for(int i=0; i < length; i++) sb.append('a'); sb.append(System.nanoTime()); return sb.toString(); } public static void main(String[] args){ List substrings = new ArrayList(); for(int i=0; i< 100; i++){ String longStr = createLongString(MB); String subStr = longStr.substring(1,10); substrings.add(subStr); } } }
上面的代碼出了什么問題呢?當(dāng)它在JDK 7u6之前的版本上運(yùn)行的時(shí)候,返回的字符串將會保存一個(gè)對那個(gè)1M左右大小的字符串的引用,如果你運(yùn)行的時(shí)候設(shè)置為-Xmx100m,你會得到一個(gè)意想不到的oom錯(cuò)誤。結(jié)合你實(shí)驗(yàn)環(huán)境中平臺和版本的差異,傷腦經(jīng)的事情就產(chǎn)生了。
現(xiàn)在如果你想掩蓋你的足跡,我們可以引進(jìn)一些更加高級的概念。比如
- 在不同的類加載器中載入有破壞性的代碼,在加載的類被原始類加載器刪除后保持對它的引用,可以模擬一個(gè)類加載器溢出
- 把攻擊性的代碼隱藏在finalize方法中,使得程序表現(xiàn)變得不可預(yù)測
- 在一個(gè)長期運(yùn)行的線程中加入棘手的組合,它可能在ThreadLocals中保存了一些可以被線程池訪問的東西,以便管理應(yīng)用線程。
我希望我們給了你一些思考的原材料以及當(dāng)你想修理某人時(shí)的一些素材。這將帶來無窮無盡的調(diào)試。除非你的朋友使用 Plumbr來查找溢出的所在地。
相關(guān)文章
IDEA實(shí)現(xiàn)序列化時(shí)如何自動生成serialVersionUID的步驟
這篇文章主要介紹了IDEA實(shí)現(xiàn)序列化時(shí)如何自動生成serialVersionUID的步驟,首先安裝GenerateSerialVersionUID插件,當(dāng)出現(xiàn)添加serialVersionUID選項(xiàng),選中則會自動生成serialVersionUID,感興趣的朋友一起學(xué)習(xí)下吧2024-02-02SpringBoot中的@RestControllerAdvice注解詳解
這篇文章主要介紹了SpringBoot中的@RestControllerAdvice注解詳解,RestControllerAdvice注解用于創(chuàng)建全局異常處理類,用于捕獲和處理整個(gè)應(yīng)用程序中的異常,需要的朋友可以參考下2024-01-01Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的示例
本文主要介紹了Spring?Cloud?Gateway編碼實(shí)現(xiàn)任意地址跳轉(zhuǎn)的示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12SpringBoot如何監(jiān)控Redis中某個(gè)Key的變化(自定義監(jiān)聽器)
這篇文章主要介紹了SpringBoot如何監(jiān)控Redis中某個(gè)Key的變化(自定義監(jiān)聽器),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09