Java內存泄漏最全詳解(六大原因及解決方案)
內存泄漏的原因
JVM 虛擬機是使用引用計數法和可達性分析來判斷對象是否可回收,本質是判斷一個對象是否還被引用,如果沒有引用則回收。
在開發的過程中,由于代碼的實現不同就會出現很多種內存泄漏問題,讓gc 系統誤以為此對象還在引用中,無法回收,造成內存泄漏。
內存泄漏的危害
- 長時間運行,程序變卡,性能嚴重下降
- 程序莫名其妙掛掉
- OutOfMemoryError錯誤
- 亂七八糟的錯誤,還不易排查
內存泄漏有哪些情況
內存泄漏原因很多,因此這里給出最常見的幾種。
1.資源未關閉造成的內存泄漏
各種連接,比如數據庫連接、網絡連接和IO連接等。
在對數據庫進行操作的過程中,首先需要建立與數據庫的連接,當不再使用時,需要調用close方法來釋放與數據庫的連接。
只有連接被關閉后,垃圾回收器才會回收對應的對象。否則,如果在訪問數據庫的過程中,對Connection、Statement或ResultSet不顯性地關閉,將會造成大量的對象無法被回收,從而引起內存泄漏,因此最好按照下面的做法處理資源類,偽代碼如下:
publicvoidhandleResource {
try {
// open connection
// handle business
} catch (Throwable t) {
// log stack
} finally {
// close connection
}}2.靜態集合類
如HashMap、LinkedList等等,如果這些容器為靜態的,那么它們的生命周期與程序一致,則容器中的對象在程序結束之前將不能被釋放,從而造成內存泄漏。
生命周期長的對象持有短生命周期對象的引用,盡管短生命周期的對象不再使用,但是因為長生命周期對象持有它的引用而導致不能被回收。
3.ThreadLocal的誤用
ThreadLocal一定要列在Java內存泄露的榜首,總能在不知不覺中將內存泄露掉,一個常見的例子是:
publicvoidtestThreadLocalMemoryLeaks {
ThreadLocal<List<Integer>> localCache = new ThreadLocal<>;
List<Integer> cacheInstance = new ArrayList<>(10000);
localCache.set(cacheInstance);localCache = new ThreadLocal<>;
}當localCache的值被重置之后cacheInstance被ThreadLocalMap中的value引用,無法被GC。
但是其key對ThreadLocal實例的引用是一個弱引用,本來ThreadLocal的實例被localCache和ThreadLocalMap的key同時引用,但是當localCache的引用被重置之后。
則ThreadLocal的實例只有ThreadLocalMap的key這樣一個弱引用了,此時這個實例在GC的時候能夠被清理。

上面這張圖詳細地揭示了ThreadLocal和Thread以及ThreadLocalMap三者的關系。
1)Thread中有一個map,就是ThreadLocalMap
2)ThreadLocalMap的key是ThreadLocal,值是我們自己設定的。
3)ThreadLocal是一個弱引用,當為null時,會被當成垃圾回收
重點來了,突然我們ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此時我們的ThreadLocalMap生命周期和Thread的一樣,它不會回收,這時候就出現了一個現象。
那就是ThreadLocalMap的key沒了,但是value還在,這就造成了內存泄漏。
解決辦法:使用完ThreadLocal后,執行remove操作,避免出現內存溢出情況。
4.變量不合理的作用域
一般而言,一個變量的定義的作用范圍大于其使用范圍,很有可能會造成內存泄漏。另一方面,如果沒有及時地把對象設置為null,很有可能導致內存泄漏的發生。
public class UsingRandom {
private String msg;
public void receiveMsg(){
readFromNet();// 從網絡中接受數據保存到msg中
saveDB();// 把msg保存到數據庫中
}
}如上面這個偽代碼,通過readFromNet方法把接受的消息保存在變量msg中,然后調用saveDB方法把msg的內容保存到數據庫中,此時msg已經就沒用了,由于msg的生命周期與對象的生命周期相同,此時msg還不能回收,因此造成了內存泄漏。
實際上這個msg變量可以放在receiveMsg方法內部,當方法使用完,那么msg的生命周期也就結束,此時就可以回收了。還有一種方法,在使用完msg后,把msg設置為null,這樣垃圾回收器也會回收msg的內存空間。
5.內部類持有外部類
如果一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄露。
6.堆外內存無法回收
堆外內存不受gc的管理,可能因為第三方的bug出現內存泄漏
內存泄漏的解決辦法
1.少使用靜態變量
盡量減少使用靜態變量,或者使用完及時 賦值為 null;
2.明確對象有效作用域
明確內存對象的有效作用域,盡量縮小對象的作用域,能用局部變量處理的不用成員變量,因為局部變量彈棧會自動回收;
3.注意聲明周期引用
減少長生命周期的對象持有短生命周期的引用;
4.注意Sting的使用
使用StringBuilder和StringBuffer進行字符串連接,Sting和StringBuilder以及StringBuffer等都可以代表字符串,其中String字符串代表的是不可變的字符串,后兩者表示可變的字符串。
如果使用多個String對象進行字符串連接運算,在運行時可能產生大量臨時字符串,這些字符串會保存在內存中從而導致程序性能下降。
5.不需要對象手動設置Null
對于不需要使用的對象手動設置null值,不管GC何時會開始清理,我們都應及時的將無用的對象標記為可被清理的對象;
6.及時關閉各種鏈接
各種連接(數據庫連接,網絡連接,IO連接)操作,務必顯示調用close關閉。



























