6.Eliminate obsolete object reference
大意為 消除舊的對象引用
當(dāng)你使用直接操作內(nèi)存的語言,例如C或者C++的時候,一些內(nèi)存釋放的操作會比較麻煩孙咪,而我們使用java這一種擁有垃圾回收機制的語言的時候落蝙,這份工作就變得輕松多了母廷,但是要注意的是斑举,這個垃圾回收機制并不能讓我們對于內(nèi)存管理掉以輕心
考慮一下下面這個棧類型的實現(xiàn)
// Can you spot the "memory leak"?
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
看上去好像沒有什么明顯的問題,測試起來也都可以通過對吧脊奋,但是注意到其中潛伏的一個問題熬北,寬泛點說,這個程序有著內(nèi)存泄漏的風(fēng)險诚隙,這樣的風(fēng)險會導(dǎo)致垃圾回收的壓力增大并且加大內(nèi)存的開銷從而降低整個程序性能讶隐,最嚴重的時候可能會產(chǎn)生OutOfMemoryError的錯誤,但是這樣的錯誤比較少見
那么是那一部分內(nèi)存泄漏呢久又,其實就是pop彈出棧操作中stack仍然保留著已經(jīng)彈出的element的引用巫延,那樣垃圾回收機制并不會去回收,并且這樣的一個舊的引用并不會被重引用籽孙,即使我們的stack沒有所有element的引用了烈评,垃圾回收機制也不會去回收,由于stack一直維持著一個舊的引用
內(nèi)存泄漏在擁有垃圾回收機制(更加適合的說法是犯建,無意的對象保留)的語言里面是十分陰險的讲冠,如果一個對象的引用無意間保留了下來,不僅僅這個對象不會被垃圾回收适瓦,那些被這個對象所引用的對象也不能被回收竿开,鏈式效應(yīng)會使得整個程序的性能極具下降
為了解決這樣的一個問題,我們只需要簡單地把那些引用置為null就可以了玻熙,比如在上述程序中否彩,我們只要這樣修改就好
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
只要你重引用這個元素,系統(tǒng)就會拋出一個空指針異常嗦随,這對于檢測程序異常錯誤十分常見
當(dāng)程序員第一次面臨這個問題的時候列荔,他們可能會在程序結(jié)束使用的時候過度去置空每一個對象的引用敬尺,這樣是既沒有必要又不被期望的,這樣會使得代碼變得雜亂贴浙,置空對象的引用應(yīng)該視情況而定而不是規(guī)范式地照搬砂吞,關(guān)于消除舊引用的最好的辦法就是讓這些包含引用的變量越界,這經(jīng)常在你在一個狹窄的范圍內(nèi)定義一個變量的時候會發(fā)生
那么崎溃,什么時候我們?nèi)ブ每者@些引用呢蜻直?簡單來說,當(dāng)你的類中所擁有的變量的引用沒有重用的可能并且你的類還繼續(xù)擁有著這個引用的話袁串,就把它置為空就可以了概而,如果不置為空,垃圾回收機制并不知道這個引用沒有作用了囱修,也就不會去回收了
總而言之赎瑰,只要當(dāng)一個類管理它自己的內(nèi)存,程序員就應(yīng)該注意一下內(nèi)存泄漏的風(fēng)險蔚袍,當(dāng)一個元素是free的時候乡范,任何對這個元素的引用都應(yīng)該被置空
另一個比較常見的可能造成內(nèi)存泄漏的原因就是緩存了配名,一旦你把一個對象的引用放到緩存里面啤咽,很容易忘記它在緩存那里并且很容易就把它一直放在緩存那里知道它變得完全沒有作用了,對于這類問題渠脉,有著一些解決方案宇整,如果你足夠幸運,實現(xiàn)的緩存的條目都是完全相關(guān)的并且只要對于鍵值存在緩存外部的引用芋膘,代表性的緩存例如WeakHashMap鳞青,一旦條目變過時了就會自動被移除,記住WeakHashMap为朋,這個類很有用當(dāng)緩存條目的生存時間取決于外部對于鍵值的引用臂拓,而不是值的引用的時候
更加常見的是,一個緩存條目的有用的生存時間很少被定義的很好习寸,隨著時間條目變得越來越?jīng)]有價值胶惰,在這種情況下,緩存應(yīng)該偶爾清一下那些不用了的條目霞溪,這可以利用后臺線程來處理(可能是一個Timer 或者是一個ScheduledThreadPoolExecutor )或者添加新的條目的時候就會有這種作用孵滞,LinkedHashMap類使用了它的removeEldestEntry方法使得后一種方法更加簡便,對于更加復(fù)雜的緩存鸯匹,你可能需要直接的使用 java.lang.ref
第三種常見的內(nèi)存泄漏的就是監(jiān)聽器和其他的回調(diào)坊饶,如果你實現(xiàn)了一個API,這個API是當(dāng)用戶注冊回調(diào)但是并沒有明確的解除注冊殴蓬,他們會積累起來除非你采取某些措施匿级,最好的辦法來保正這些回調(diào)被垃圾回收及時處理就是只儲存weak reference(弱引用),對于實例,就利用WeakHashMap儲存他們作為鍵
因為內(nèi)存泄漏不會特別的明顯地顯示出來痘绎,他們可能在某個系統(tǒng)里面潛藏很久脓杉,只有十分仔細的代碼或者debug工具(比如heap profiler)的輔助才能發(fā)現(xiàn)他們,因此简逮,我們需要學(xué)習(xí)去在這些問題發(fā)生之前預(yù)測并且防止他們發(fā)生