這兩天看了一本老書《bitter java》,第一次系統地了解了所謂“反模式”。就書的内容來說已經過于陳舊,書中提到的magic servlet、複合jsp等等反模式已經是早就熟知的程式設計禁忌,而如web頁面不能有太多元素這樣的反模式也因為ajax的出現(異步加載)變的不是那麼“反模式”了,其中又講述了很多ejb的反模式,這些在輕量級架構流行的今天也早已經過時。不過書中有一個章節倒是挺有價值,講述的是java的記憶體洩露問題,我認為是我目前讀的關于這方面問題比較有價值的介紹。
網上關于java記憶體洩露的資料都過于玄乎,其實java導緻記憶體洩露的原因很明确:長生命周期的對象持有短生命周期對象的引用就很可能發生記憶體洩露,盡管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導緻不能被回收,這就是java中記憶體洩露的發生場景。作者在書中提到了3個場景:
1。流失監聽器問題,在awt、swing程式設計中,給元件添加了事件監聽器,這些元件的生命周期如果很長的話,監聽器對象将不能被正确回收。關于gui程式設計我不是很熟悉,這一點存有疑問,因為顯然你觸發一個按鈕的事件,當然是一直期待同樣的行為發生,如果删除了監聽器或者使用弱引用讓jvm回收不符合業務邏輯和使用者體驗。
2。集合類,集合類僅僅有添加元素的方法,而沒有相應的删除機制,導緻記憶體被占用。這一點其實也不明确,這個集合類如果僅僅是局部變量,根本不會造成記憶體洩露,在方法棧退出後就沒有引用了會被jvm正常回收。而如果這個集合類是全局性的變量(比如類中的靜态屬性,全局性的map等),那麼沒有相應的删除機制,很可能導緻集合所占用的記憶體隻增不減,是以提供這樣的删除機制或者定期清除政策非常必要。
3。單例模式。不正确使用單例模式是引起記憶體洩露的一個常見問題,單例對象在被初始化後将在jvm的整個生命周期中存在(以靜态變量的方式),如果單例對象持有外部對象的引用,那麼這個外部對象将不能被jvm正常回收,導緻記憶體洩露,考慮下面的例子:
class a{
public a(){
b.getinstance().seta(this);
}
....
}
//b類采用單例模式
class b{
private a a;
private static b instance=new b();
public b(){}
public static b getinstance(){
return instance;
}
public void seta(a a){
this.a=a;
//getter...
顯然b采用singleton模式,他持有一個a對象的引用,而這個a類的對象将不能被回收。想象下如果a是個比較大的對象或者集合類型會發生什麼情況。
上面所講的這些也啟發我們如何去查找記憶體洩露問題,第一選擇當然是利用工具,比如jprofiler,第二就是在代碼複審的時候關注長生命周期對象:全局性的集合、單例模式的使用、類的static變量等等。
文章轉自莊周夢蝶 ,原文釋出時間2007-11-11