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

