一锁蠕、什么是內(nèi)存泄漏夷野?
這句話可能有點(diǎn)繞:當(dāng)一個(gè)長(zhǎng)生命周期的對(duì)象持有一個(gè)短生命周期的對(duì)象的引用時(shí),導(dǎo)致短生命周期的對(duì)象無(wú)法被回收荣倾,而導(dǎo)致我們寶貴的內(nèi)存被占用,最終OOM,全稱“Out Of Memory”,翻譯成中文就是“內(nèi)存用完了悯搔。
大家都知道在JAVA中有著名的垃圾回收器GC,那么GC是如何認(rèn)識(shí)垃圾的呢舌仍?有兩種常用的方式1.引用計(jì)數(shù)法? 2.根可達(dá)算法妒貌,可無(wú)論是引用計(jì)數(shù)還是根可達(dá)算法都是根據(jù)引用來(lái)判斷對(duì)象到底是不是一個(gè)垃圾,要不要回收它铸豁,可現(xiàn)在有這么一種情況? 如圖所示:
對(duì)象A生命周期明明是到T1就結(jié)束了(引用應(yīng)該釋放掉灌曙,被GC發(fā)現(xiàn)應(yīng)該回收內(nèi)存),但是生命周期為T2的對(duì)象B霸占了對(duì)象A的引用導(dǎo)致對(duì)象A不能被回收节芥。
2在刺、內(nèi)存泄漏的具體情況
(1)靜態(tài)集合類:
如HashMap、LinkedList头镊、ArrayList等等蚣驼。如果這些容器為靜態(tài)的,那么它們的生命周期與程序一致相艇,則容器中的對(duì)象在程序結(jié)束之前將不能被釋放颖杏,從而造成內(nèi)存泄漏。簡(jiǎn)單而言坛芽,長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用留储,盡管短生命周期的對(duì)象不再使用,但是因?yàn)殚L(zhǎng)生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收咙轩,解決辦法是最好不使用靜態(tài)的集合類获讳,如果使用的話,在不需要容器時(shí)要將其賦值為null活喊。
(2)創(chuàng)建的各種連接赔嚎,數(shù)據(jù)庫(kù)連接,IO連接等等:
例如我們?cè)谶B接數(shù)據(jù)庫(kù)的時(shí)候像JDBC六大步胧弛,注冊(cè)驅(qū)動(dòng)尤误,獲取連接,創(chuàng)建連接對(duì)象结缚,執(zhí)行sql损晤,處理結(jié)果集,釋放資源红竭,這是時(shí)候的釋放資源調(diào)用close()方法就是告訴GC尤勋,這個(gè)連接對(duì)象我不用了喘落,你可以回收了,如果你不釋放最冰,GC是無(wú)法判定你有沒(méi)有使用完連接對(duì)象的瘦棋,自然不能幫你釋放資源,從而暖哨,你在使用數(shù)據(jù)庫(kù)的時(shí)候會(huì)產(chǎn)生大量的對(duì)象赌朋,對(duì)資源進(jìn)行浪費(fèi),導(dǎo)致內(nèi)存泄漏篇裁。
(3)變量不合理的作用域
可以聲明為局部變量(在方法中)絕不聲明為成員變量(在類中) 因?yàn)榫植孔兞渴请S著方法的調(diào)用而創(chuàng)建沛慢,隨著方法的銷毀而銷毀,而定義為成員變量是隨著類在變化达布,在方法中用完之后沒(méi)有設(shè)為null值团甲,那么便會(huì)隨著類一直存在造成內(nèi)存泄漏。
(4)內(nèi)部類持有外部類
如果一個(gè)外部類的實(shí)例對(duì)象的方法返回了一個(gè)內(nèi)部類的實(shí)例對(duì)象黍聂,這個(gè)內(nèi)部類對(duì)象被長(zhǎng)期引用了躺苦,即使那個(gè)外部類實(shí)例對(duì)象不再被使用,但由于內(nèi)部類持有外部類的實(shí)例對(duì)象产还,這個(gè)外部類對(duì)象將不會(huì)被垃圾回收匹厘,這也會(huì)造成內(nèi)存泄露。
(5)緩存泄漏
內(nèi)存泄漏的另一個(gè)常見(jiàn)來(lái)源是緩存雕沉,一旦你把對(duì)象引用放入到緩存中集乔,他就很容易遺忘去件,對(duì)于這個(gè)問(wèn)題坡椒,可以使用WeakHashMap代表緩存,此種Map的特點(diǎn)是尤溜,當(dāng)除了自身有對(duì)key的引用外倔叼,此key沒(méi)有其他引用那么此map會(huì)自動(dòng)丟棄此值
``
public class Test {
public static void main(String[] args) {
String a =new String("a");
String b =new String("b");
Map map =new HashMap();
map.put(a,"aaa");
map.put(b,"bbb");
Map weakmap =new WeakHashMap();
weakmap.put(a,"aaa");
weakmap.put(b,"bbb");
map.remove(a);
a =null; b =null;
System.gc();
map.forEach((i1,i2)->{
System.out.println("map:");
System.out.println(i1+":"+i2);});
weakmap.forEach((i1,i2)->{
System.out.println("weakmap:");
System.out.println(i1+":"+i2);
});
}
}
``
輸出結(jié)果如下,看一下代碼你發(fā)下只是移除了map中的a宫莱,weakmap中的a也消失了
(6)改變哈希值
當(dāng)一個(gè)對(duì)象被存儲(chǔ)進(jìn)HashSet集合中以后丈攒,就不能修改這個(gè)對(duì)象中的那些參與計(jì)算哈希值的字段了,否則授霸,對(duì)象修改后的哈希值與最初存儲(chǔ)進(jìn)HashSet集合中時(shí)的哈希值就不同了巡验,在這種情況下,即使在contains方法使用該對(duì)象的當(dāng)前引用作為的參數(shù)去HashSet集合中檢索對(duì)象碘耳,也將返回找不到對(duì)象的結(jié)果显设,這也會(huì)導(dǎo)致無(wú)法從HashSet集合中單獨(dú)刪除當(dāng)前對(duì)象,造成內(nèi)存泄露
(7)單例模式可能會(huì)造成內(nèi)存泄露
? ? 單例模式只允許應(yīng)用程序存在一個(gè)實(shí)例對(duì)象辛辨,并且這個(gè)實(shí)例對(duì)象的生命周期和應(yīng)用程序的生命周期一樣長(zhǎng)捕捂,如果單例對(duì)象中擁有另一個(gè)對(duì)象的引用的話瑟枫,這個(gè)被引用的對(duì)象就不能被及時(shí)回收。解決辦法是單例對(duì)象中持有的其他對(duì)象使用弱引用指攒,弱引用對(duì)象在GC線程工作時(shí)慷妙,其占用的內(nèi)存會(huì)被回收掉,不知道什么是弱引用的可以看我之前文章允悦,強(qiáng)軟弱虛膝擂。
(8)監(jiān)聽(tīng)器和回調(diào)
內(nèi)存泄漏第三個(gè)常見(jiàn)來(lái)源是監(jiān)聽(tīng)器和其他回調(diào),如果客戶端在你實(shí)現(xiàn)的API中注冊(cè)回調(diào)澡屡,卻沒(méi)有顯示的取消猿挚,那么就會(huì)積聚。需要確笔火模回調(diào)立即被當(dāng)作垃圾回收的最佳方法是只保存他的若引用绩蜻,例如將他們保存成為WeakHashMap中的鍵。