所謂記憶體外洩就是指一個不再被程式便用的物件或變數一直被佔據在記憶體中。
Java 中有垃圾回收機制,它可以保證一物件不再被引用的時候,也就是物件變成了孤兒的時候,物件就會自動被垃圾回收器從記憶體中清除掉。
既然java有垃圾回收機制,為什麼還會有記憶體洩漏的問題呢?
無非,就是有些對象,無法被垃圾回收器處理,導致這些對像一直佔用JVM內存,那不就導致內存洩漏了嘛。
由於Java 使用有向圖的方式進行垃圾回收管理,可以消除引用循環的問題,例如有兩個對象,相互引用, 只要它們和根進程不可達的,那麼GC也是可以回收它們的,例如下面的程式碼可以看到這種情況的記憶體回收。
import java. io.IOException; public class GarbageTest { public static void main(String[] args) throws IOException { try { // TODO Auto-generated method stub gcTest(); } catch (IOException e) { e.printStackTrace(); } System.out.println("has exited gcTest!"); System.in.read(); System.in.read(); System.out.println("out begin gc!"); for (int i = 0; i < 100; i++) { System.gc(); System.in.read(); System.in.read(); } } private static void gcTest() throws IOException { System.in.read(); System.in.read(); Person p1 = new Person(); System.in.read(); System.in.read(); Person p2 = new Person(); p1.setMate(p2); p2.setMate(p1); System.out.println("before exit gctest!"); System.in.read(); System.in.read(); System.gc(); System.out.println("exit gctest!"); } private static class Person { byte[] data = new byte[20000000]; Person mate = null; public void setMate(Person other) { mate = other; } } }
Java 中的記憶體洩露的情況: 長生命週期的物件持有短生命週期物件的引用就很可能發生記憶體洩露,儘管短室命週期物件已經不再需要,但是因為長生命週期對象持有它的引用而導致不能被回收,這就是Java 中內存洩露的發室場景,通俗地說,就是程序員可能創建了一個對象,以後一直不再使用這個對象,這個對象卻一直被引用,即這個物件無用但是卻無法被垃圾回收器回收的,這就是java 中可能出現記憶體洩漏的情況。
例如,快取系統,我們載入了一個物件放在快取中(例如放在一個全域map物件中),然後一直不再使用它,這個物件一值被快取引用, 但卻不再被便用。
檢查Java 中的記憶體洩露,一定要讓程式將各種分支情況都完整執行到程式結束,然後看某個物件是否被使用過,如果沒有,則才能判定這個物件屬於記憶體洩露。
如果一個外部類別的實例物件的方法傳回了一個內部類別的實例對象,這個內部類別物件被長期引用了,即使那個外部類別實例物件不再被使用,但由於內部類別持久外部類別的實例對象,這個外部類別物件將不會被垃圾回收,這也會造成記憶體洩漏.
下面內容來自於網路上(主要特點就是清空堆疊中的某個元素,並不是徹底把它從陣列中拿掉,而是把儲存的總數減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從數組中消失,將那個元素所在的位置的值設為null 即可)
我實在想不到比那個堆疊更經典的例子了,以致於我還要引用別人的例子,下面的例子不是我想到的,是書上看到的,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒人相信的。
public class Stack { private Object[] elements = new Object[10]; private int size = 0; public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if (elements.length == size) { Object[] oldElements = elements; elements = new Object[2 * elements.length + 1]; System.arraycopy(oldElements, 0, elements, 0, size); } } }
上面的原理應該很簡單,假如堆錢加了10 個元素,然後全部彈出來,雖然堆錢是空的,沒有我們要的東西,但是這是個物件是無法回收的,這個才符合了記憶體外洩的兩個條件無用,無法回收。但就是存在這樣的東西也不一定會導致什麼樣的後果,如果這個堆錢用的比較少,也就浪費了幾個K 內存而己,反正我們的內存都上G 了,哪裡會有什麼影響,再說這個東西很快就會被回收的,有什麼關係。下面看兩個例子。
class Bad { public static Stack s = new Stack(); static { s.push(new Object()); s.pop(); //这里有一个对象发生内存泄露 s.push(new Object());//上面的对象可以被回收了,等于是自愈了 } }
因為是static ,就一直存在到程式退出,但我們也可以看到它有自癒功能,就是說如果你的Stack 最多有100 個對象,那麼最多也就只有100 個對象無法被回收, 其實這個應該很容易理解,Stack 內部持有100 個引用,最壞的情況就是他們都是無用的,因為我們一旦放新的進取,以前的引用自然消失!
記憶體外洩的另一個情況: 當一個物件儲存進HashSet 集合中以後,就不能修改這個物件中的那些參與計算哈希值的欄位了,否則,物件修改後的晗希值與最初儲存進HashSet 集合中時的哈希值就不同了,在這種情況下,即使在contains 方法使用該對象的當前引用作為的參數去HashSet 集合中檢索對象, 也將返回找不到對象的結果,這也會導致無法從HashSet 集合中單獨刪除當前對象,造成記憶體外洩。
(1) 資料結構造成的短暫記憶體外洩問題,看下面的程式碼
public class Stack{ private Object[] element=new Object[10]; private int size=0; public void push(Object ele){ ensureCapacity(); element[size++]=ele; } public Object pop(){ if(size==0) throw new EmptyStackException(); return element[--size]; //短暂造成内存泄露 } private void ensureCapacity(){ if(element.length==size){ Object[] oldElement=element; element=new Object[size*2+1]; System.arraycopy(oldElement,0,element,0,size); } } }
上面的程式碼每一次pop()的時候,Stack都會彈出一個元素,在沒有加入新元素之前,實際上仍然有一個引用element[x]指向了這個已經彈出的對象,因此GC是不會對其進行垃圾回收的。只有push()新元素的時候使得element[x]=newObject,才會使得先前建立的物件有可能被回收。應該把上面的pop()方法改成下面的程式碼就安全多了:
public Object pop(){ if(element.length==size) throws EmptyStackException(); Object o=element[--size]; elements[size]=null; //使得GC有机会回收这个对象 return o; }
以上是Java記憶體外洩問題實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!