這兩天看了一本老書《bitter java》,第一次系統(tǒng)地了解了所謂“反模式”。就書的內(nèi)容來說已經(jīng)過于陳舊,書中提到的magic servlet、復(fù)合jsp等等反模式已經(jīng)是早就熟知的編程禁忌,而如web頁面不能有太多元素這樣的反模式也因為ajax的出現(xiàn)(異步加載)變的不是那么“反模式”了,其中又講述了很多ejb的反模式,這些在輕量級框架流行的今天也早已經(jīng)過時。不過書中有一個章節(jié)倒是挺有價值,講述的是java的內(nèi)存泄露問題,我認為是我目前讀的關(guān)于這方面問題比較有價值的介紹。
網(wǎng)上關(guān)于java內(nèi)存泄露的資料都過于玄乎,其實java導(dǎo)致內(nèi)存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場景。作者在書中提到了3個場景:
1。流失監(jiān)聽器問題,在awt、swing編程中,給組件添加了事件監(jiān)聽器,這些組件的生命周期如果很長的話,監(jiān)聽器對象將不能被正確回收。關(guān)于GUI編程我不是很熟悉,這一點存有疑問,因為顯然你觸發(fā)一個按鈕的事件,當然是一直期待同樣的行為發(fā)生,如果刪除了監(jiān)聽器或者使用弱引用讓JVM回收不符合業(yè)務(wù)邏輯和用戶體驗。
2。集合類,集合類僅僅有添加元素的方法,而沒有相應(yīng)的刪除機制,導(dǎo)致內(nèi)存被占用。這一點其實也不明確,這個集合類如果僅僅是局部變量,根本不會造成內(nèi)存泄露,在方法棧退出后就沒有引用了會被jvm正常回收。而如果這個集合類是全局性的變量(比如類中的靜態(tài)屬性,全局性的map等),那么沒有相應(yīng)的刪除機制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減,因此提供這樣的刪除機制或者定期清除策略非常必要。
3。單例模式。不正確使用單例模式是引起內(nèi)存泄露的一個常見問題,單例對象在被初始化后將在JVM的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用,那么這個外部對象將不能被jvm正常回收,導(dǎo)致內(nèi)存泄露,考慮下面的例子:
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是個比較大的對象或者集合類型會發(fā)生什么情況。
上面所講的這些也啟發(fā)我們?nèi)绾稳ゲ檎覂?nèi)存泄露問題,第一選擇當然是利用工具,比如jprofiler,第二就是在代碼復(fù)審的時候關(guān)注長生命周期對象:全局性的集合、單例模式的使用、類的static變量等等。
網(wǎng)上關(guān)于java內(nèi)存泄露的資料都過于玄乎,其實java導(dǎo)致內(nèi)存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場景。作者在書中提到了3個場景:
1。流失監(jiān)聽器問題,在awt、swing編程中,給組件添加了事件監(jiān)聽器,這些組件的生命周期如果很長的話,監(jiān)聽器對象將不能被正確回收。關(guān)于GUI編程我不是很熟悉,這一點存有疑問,因為顯然你觸發(fā)一個按鈕的事件,當然是一直期待同樣的行為發(fā)生,如果刪除了監(jiān)聽器或者使用弱引用讓JVM回收不符合業(yè)務(wù)邏輯和用戶體驗。
2。集合類,集合類僅僅有添加元素的方法,而沒有相應(yīng)的刪除機制,導(dǎo)致內(nèi)存被占用。這一點其實也不明確,這個集合類如果僅僅是局部變量,根本不會造成內(nèi)存泄露,在方法棧退出后就沒有引用了會被jvm正常回收。而如果這個集合類是全局性的變量(比如類中的靜態(tài)屬性,全局性的map等),那么沒有相應(yīng)的刪除機制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減,因此提供這樣的刪除機制或者定期清除策略非常必要。
3。單例模式。不正確使用單例模式是引起內(nèi)存泄露的一個常見問題,單例對象在被初始化后將在JVM的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用,那么這個外部對象將不能被jvm正常回收,導(dǎo)致內(nèi)存泄露,考慮下面的例子:
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是個比較大的對象或者集合類型會發(fā)生什么情況。
上面所講的這些也啟發(fā)我們?nèi)绾稳ゲ檎覂?nèi)存泄露問題,第一選擇當然是利用工具,比如jprofiler,第二就是在代碼復(fù)審的時候關(guān)注長生命周期對象:全局性的集合、單例模式的使用、類的static變量等等。