如圖所示的例子,這段程序中沒有明顯的錯誤,但是存在一個隱藏的問題(“內存泄漏”),隨著垃圾回收活動的增加,或者由于內存占用的不斷增加,程序性能的降低會逐漸表現出來,在極端的情況下,這種內存泄漏會導致磁盤交換,甚至導致程序失敗。
如果棧先是增長,然后在收縮,那么從棧中彈出的對象不會被當做垃圾回收,即使使用棧的程序不在引用這些對象,他們也不會被回收。因為,棧內部維護著對這些對象的過期引用。過期引用是指永遠也不會解除的引用。在以上這個例子中,elements數組的活動部分之外的所有引用都是過期的?;顒硬糠质侵竐lements數組中下標小于size的部分。
如果一個對象引用被無意識的保留下來,那么垃圾回收機制不僅不會處理這個對象,而且也不會處理處理這個對象所引用的所有其他對象。即使少量的幾個對象被無意識的保留下來,也許會有許多對象被排除在垃圾回收之外,并由此對性能造成重大影響。
解決方法:一旦引用對象已經過期,清空這些引用。
例子中的statck類,只要一個單元被彈出,指向他的引用就過期了,pop方法修改為
public object pop(){
if(size ==0){
throw new EmptyStackException();
}
ObJect result = elements[--size]//--放在前面為先減一再使用
element[size] = null;
return result;
}
清空過期引用的另外一個好處是,如果他們以后又被錯誤的解除引用,程序會立即拋出空指針異常,而不是悄悄的錯誤的運行下去。
清空對象引用應該是一種例外,而不是一種規范行為。消除過期引用的最好的方法是讓包含該引用的變量結束其生命周期。如果在最緊湊的作用域范圍內定義一個變量
如下例 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的生命周期循環結束后結束
stack類自己管理內存,存儲池包含了elements數組的元素,數組活動區域中的元素是已分配的,而數組其余部分的元素是自由的,但是垃圾回收器并不知道這一點,對于垃圾回收來說,elements數組中的所有對象引用都同等有效。只有我們本身知道數組的非活動部分是不重要的,所以需要我們手動清空這些元素。
一般而言,只要是自己管理內存,就應該警惕內存泄漏問題。一旦元素被釋放掉,則該元素中包含的任何對象引用都應被清空掉。
內存泄漏的另一個常見的來源是緩存,一旦把對象引用放入緩存中,它就很容易被遺忘。對于這種情況有幾種可能的解決方案,如果你正好要實現一個只要在緩存之外存在對某個項的鍵的引用,該項就有意義這樣的緩存的話,就可以使用WeakHashMap代表緩存,因為當緩存中的項過期的時候,它們就會自動被刪除掉。但是只有當所要的緩存項的生命周期是由key的外部引用而不是由value決定時WeakHashMap才有用處。
更常見的情況是“緩存項的生命周期是否有意義”,并不是很容易確定,隨著時間的推移,其中的項會變得越來越沒有價值,在這種情況下,緩存應該時不時地清除掉沒用的項,這種工作可以交給一個后臺線程(可能是Timer或者ScheduledThreadPoolExecutor)來完成,或者也可以在給緩存添加新條目的時候順便進行。LinkedHashMap類利用它的removeEldestEntry方法可以很容易的實現后一種方案,對于更加復雜的緩存,就只能直接使用java.lang.ref了。
內存泄漏的第三種常見來源是監聽器和其他的回調函數。如果客戶在你實現的API中注冊回調,但是卻沒有顯示取消注冊。那么除非你采取些手段,否則它們就會積聚。確?;卣{立即被當作垃圾的最佳方法是只保存它們的弱引用,例如,只將他們保存為WeakHashMap中的鍵。
WeakHashmap 的簡單介紹
1. 以弱鍵 實現的基于哈希表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。更精確地說,對于一個給定的鍵,其映射的存在并不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,然后被回收。丟棄某個鍵時,其條目從映射中有效地移除
2. WeakHashMap 類的行為部分取決于垃圾回收器的動作。因為垃圾回收器在任何時候都可能丟棄鍵,WeakHashMap 就像是一個被悄悄移除條目的未知線程。特別地,即使對 WeakHashMap 實例進行同步,并且沒有調用任何賦值方法,在一段時間后 size 方法也可能返回較小的值,對于 isEmpty 方法,返回 false,然后返回true,對于給定的鍵,containsKey 方法返回 true 然后返回 false,對于給定的鍵,get 方法返回一個值,但接著返回 null,對于以前出現在映射中的鍵,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),有時候我們也稱為堆棧(這一點可能會讓很多小伙伴迷茫)。是由操作系統自動分配和釋放的,用來存放局部變量,基本類型的值(比如int,char,boolean等),因為它是操作系統自動分配和釋放的,所以通常我們看不到棧的操作。另外,棧是先進先出的。
堆(heap)。由程序員自己來分配和釋放。用來存放用new創建的對象和數組。注意,前面我們說了“由程序員自己來分配和釋放”,實際上在Java里面,是由程序員自己來分配的(new),但是不是由程序員自己來釋放的,而是通過GC(垃圾回收器)來完成釋放的,程序員完全感知不到。
方法區(method)。又叫靜態區,用來存放在整個程序中都是唯一的元素,比如所有的class,以及static變量