如圖所示的例子揍障,這段程序中沒有明顯的錯誤,但是存在一個隱藏的問題(“內存泄漏”)畸冲,隨著垃圾回收活動的增加,或者由于內存占用的不斷增加观腊,程序性能的降低會逐漸表現(xiàn)出來邑闲,在極端的情況下,這種內存泄漏會導致磁盤交換梧油,甚至導致程序失敗苫耸。
如果棧先是增長,然后在收縮儡陨,那么從棧中彈出的對象不會被當做垃圾回收褪子,即使使用棧的程序不在引用這些對象,他們也不會被回收骗村。因為嫌褪,棧內部維護著對這些對象的過期引用。過期引用是指永遠也不會解除的引用胚股。在以上這個例子中笼痛,elements數組的活動部分之外的所有引用都是過期的。活動部分是指elements數組中下標小于size的部分缨伊。
如果一個對象引用被無意識的保留下來摘刑,那么垃圾回收機制不僅不會處理這個對象,而且也不會處理處理這個對象所引用的所有其他對象刻坊。即使少量的幾個對象被無意識的保留下來枷恕,也許會有許多對象被排除在垃圾回收之外,并由此對性能造成重大影響谭胚。
解決方法:一旦引用對象已經過期徐块,清空這些引用。
例子中的statck類漏益,只要一個單元被彈出蛹锰,指向他的引用就過期了,pop方法修改為
public object pop(){
if(size ==0){
throw new EmptyStackException();
}
ObJect result = elements[--size]//--放在前面為先減一再使用
element[size] = null;
return result;
}
清空過期引用的另外一個好處是绰疤,如果他們以后又被錯誤的解除引用铜犬,程序會立即拋出空指針異常,而不是悄悄的錯誤的運行下去轻庆。
清空對象引用應該是一種例外癣猾,而不是一種規(guī)范行為。消除過期引用的最好的方法是讓包含該引用的變量結束其生命周期余爆。如果在最緊湊的作用域范圍內定義一個變量
如下例 1
Iterator iter = l.iterator();
while(iter.hasNext()){
String str = (String) iter.next();
System.out.println(str);
}
例2
while(Iterator iter = l.iterator();iter.hasNext()){
String str = (String) iter.next();
System.out.println(str);
}
例2這種就比較合適纷宇,iter的生命周期循環(huán)結束后結束
stack類自己管理內存,存儲池包含了elements數組的元素蛾方,數組活動區(qū)域中的元素是已分配的像捶,而數組其余部分的元素是自由的,但是垃圾回收器并不知道這一點桩砰,對于垃圾回收來說拓春,elements數組中的所有對象引用都同等有效。只有我們本身知道數組的非活動部分是不重要的亚隅,所以需要我們手動清空這些元素硼莽。
一般而言,只要是自己管理內存煮纵,就應該警惕內存泄漏問題懂鸵。一旦元素被釋放掉,則該元素中包含的任何對象引用都應被清空掉行疏。
內存泄漏的另一個常見的來源是緩存匆光,一旦把對象引用放入緩存中,它就很容易被遺忘隘擎。對于這種情況有幾種可能的解決方案殴穴,如果你正好要實現(xiàn)一個只要在緩存之外存在對某個項的鍵的引用,該項就有意義這樣的緩存的話货葬,就可以使用WeakHashMap代表緩存采幌,因為當緩存中的項過期的時候,它們就會自動被刪除掉震桶。但是只有當所要的緩存項的生命周期是由key的外部引用而不是由value決定時WeakHashMap才有用處休傍。
更常見的情況是“緩存項的生命周期是否有意義”,并不是很容易確定蹲姐,隨著時間的推移磨取,其中的項會變得越來越沒有價值,在這種情況下柴墩,緩存應該時不時地清除掉沒用的項忙厌,這種工作可以交給一個后臺線程(可能是Timer或者ScheduledThreadPoolExecutor)來完成,或者也可以在給緩存添加新條目的時候順便進行江咳。LinkedHashMap類利用它的removeEldestEntry方法可以很容易的實現(xiàn)后一種方案逢净,對于更加復雜的緩存,就只能直接使用java.lang.ref了歼指。
內存泄漏的第三種常見來源是監(jiān)聽器和其他的回調函數爹土。如果客戶在你實現(xiàn)的API中注冊回調,但是卻沒有顯示取消注冊踩身。那么除非你采取些手段胀茵,否則它們就會積聚。確毙瑁回調立即被當作垃圾的最佳方法是只保存它們的弱引用琼娘,例如,只將他們保存為WeakHashMap中的鍵附鸽。
WeakHashmap 的簡單介紹
1. 以弱鍵 實現(xiàn)的基于哈希表的 Map脱拼。在 WeakHashMap 中,當某個鍵不再正常使用時拒炎,將自動移除其條目挪拟。更精確地說,對于一個給定的鍵击你,其映射的存在并不阻止垃圾回收器對該鍵的丟棄玉组,這就使該鍵成為可終止的,被終止丁侄,然后被回收惯雳。丟棄某個鍵時,其條目從映射中有效地移除
2. WeakHashMap 類的行為部分取決于垃圾回收器的動作鸿摇。因為垃圾回收器在任何時候都可能丟棄鍵石景,WeakHashMap 就像是一個被悄悄移除條目的未知線程。特別地,即使對 WeakHashMap 實例進行同步潮孽,并且沒有調用任何賦值方法揪荣,在一段時間后 size 方法也可能返回較小的值,對于 isEmpty 方法往史,返回 false仗颈,然后返回true,對于給定的鍵椎例,containsKey 方法返回 true 然后返回 false挨决,對于給定的鍵,get 方法返回一個值订歪,但接著返回 null脖祈,對于以前出現(xiàn)在映射中的鍵,put 方法返回 null刷晋,而 remove 方法返回 false盖高,對于鍵 set、值 collection 和條目 set 進行的檢查掏秩,生成的元素數量越來越少或舞。
3. WeakHashMap 中的每個鍵對象間接地存儲為一個弱引用的指示對象。因此蒙幻,不管是在映射內還是在映射之外映凳,只有在垃圾回收器清除某個鍵的弱引用之后,該鍵才會自動移除邮破。
例3
BufferedReader br=newBufferedReader(newFileReader(file));//構造一個BufferedReader類來讀取文件
String s =null;
String s1 =""
while((s = br.readLine())!=null){//使用readLine方法诈豌,一次讀一行
s1 = s1+s
result.append(System.lineSeparator()+s);
}
此例是曾經寫過的一個列子,用s用來讀取文件中的行的內容抒和,用s1進行接收矫渔,當文件中的內容過多時,會導致內存溢出
PS:補充一下堆棧的基礎知識摧莽。
棧(stack)庙洼,有時候我們也稱為堆棧(這一點可能會讓很多小伙伴迷茫)。是由操作系統(tǒng)自動分配和釋放的镊辕,用來存放局部變量油够,基本類型的值(比如int,char征懈,boolean等)石咬,因為它是操作系統(tǒng)自動分配和釋放的,所以通常我們看不到棧的操作卖哎。另外鬼悠,棧是先進先出的删性。
堆(heap)。由程序員自己來分配和釋放焕窝。用來存放用new創(chuàng)建的對象和數組蹬挺。注意,前面我們說了“由程序員自己來分配和釋放”袜啃,實際上在Java里面汗侵,是由程序員自己來分配的(new)幸缕,但是不是由程序員自己來釋放的群发,而是通過GC(垃圾回收器)來完成釋放的,程序員完全感知不到发乔。
方法區(qū)(method)熟妓。又叫靜態(tài)區(qū),用來存放在整個程序中都是唯一的元素栏尚,比如所有的class起愈,以及static變量