深入聊聊Java內(nèi)存泄露問題
Java內(nèi)存泄露問題
所謂內(nèi)存泄露就是指一個不再被程序便用的對象或變量一直被占據(jù)在內(nèi)存中。
Java 中有垃圾回收機制,它可以保證一對象不再被引用的時候,即對象變成了孤兒的時候,對象將自動被垃圾回收器從內(nèi)存中清除掉。
既然java有垃圾回收機制,為什么還會存在內(nèi)存泄漏的問題呢?
無非,就是有些對象,無法被垃圾回收器處理,導(dǎo)致這些對象一直占用JVM內(nèi)存,那不就導(dǎo)致內(nèi)存泄漏了嘛。
由于 Java 使用有向圖的方式進行垃圾回收管理,可以消除引用循環(huán)的問題 ,例如有兩個對象 ,相互引用, 只要它們和根進程不可達的,那么GC也是可以回收它們的,例如下面的代碼可以看到這種情況的內(nèi)存回收。
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 中的內(nèi)存泄露的情況: 長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短室命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是 Java 中內(nèi)存泄露的發(fā)室場景 ,通俗地說,就是程序員可能創(chuàng)建了一個對象,以后一直不再使用這個對象,這個對象卻一直被引用,即這個對象無用但是卻無法被垃圾回收器回收的,這就是 java 中可能出現(xiàn)內(nèi)存泄露的情況。
例如,緩存系統(tǒng),我們加載了一個對象放在緩存中 (例如放在一個全局map對象中),然后一直不再使用它,這個對象一值被緩存引用, 但卻不再被便用。
檢查 Java 中的內(nèi)存泄露,一定要讓程序?qū)⒏鞣N分支情況都完整執(zhí)行到程序結(jié)束,然后看某個對象是否被使用過,如果沒有,則才能判定這個對象屬于內(nèi)存泄露。
如果一個外部類的實例對象的方法返回了一個內(nèi)部類的實例對象,這個內(nèi)部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由于內(nèi)部類持久外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內(nèi)存泄露.
下面內(nèi)容來自于網(wǎng)上 (主要特點就是清空堆棧中的某個元素,并不是徹底把它從數(shù)組中拿掉,而是把存儲的總數(shù)減少,本人寫得可以比這個好,在拿掉某個元素時,順便也讓它從數(shù)組中消失,將那個元素所在的位置的值設(shè)置為 null 即可)
我實在想不到比那個堆棧更經(jīng)典的例子了,以致于我還要引用別人的例子,下面的例子不是我想到的,是書上看到的 ,當然如果沒有在書上看到,可能過一段時間我自己也想的到,可是那時我說是我自己想到的也沒有人相信的。
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); } } }
上面的原理應(yīng)該很簡單,假如堆錢加了10 個元素 ,然后全部彈出來 ,雖然堆錢是空的,沒有我們要的東西,但是這是個對象是無法回收的,這個才符合了內(nèi)存泄露的兩個條件 無用,無法回收。 但是就是存在這樣的東西也不一定會導(dǎo)致什么樣的后果 ,如果這個堆錢用的比較少,也就浪費了幾個 K 內(nèi)存而己,反正我們 的內(nèi)存都上 G 了,哪里會有什么影響,再說這個東西很快就會被回收的,有什么關(guān)系。 下面看兩個例子。
class Bad { public static Stack s = new Stack(); static { s.push(new Object()); s.pop(); //這里有一個對象發(fā)生內(nèi)存泄露 s.push(new Object());//上面的對象可以被回收了,等于是自愈了 } }
因為是 static ,就一直存在 到程序退出,但是我們也可以看到它有自愈功能,就是說如果你的 Stack 最多有 100 個對象,那么最多也就只有 100 個對象無法 被回收, 其實這個應(yīng)該很容易理解,Stack 內(nèi)部持有 100 個引用,最壞的情況就是 他們都是無用的,因為我們一旦放新的進取,以前的引用自然消失!
內(nèi)存泄露的另外一種情況: 當一個對象被存儲進 HashSet 集合中以后,就不能修改這個對象中的那些參與計算哈希值的字段了,否則,對象修改后的晗希值與 最初存儲進 HashSet 集合中時的哈希值就不同了,在這種情況下,即使在 contains 方法使用該對象的當前引用作為的參數(shù)去 HashSet 集合中檢索對象, 也將返回找不到對象的結(jié)果,這也會導(dǎo)致無法從 HashSet 集合中單獨刪除當前對象,造成內(nèi)存泄露。
附:內(nèi)存泄露的典型情況
(1) 數(shù)據(jù)結(jié)構(gòu)造成的短暫內(nèi)存泄露問題,看下面的代碼
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]; //短暫造成內(nèi)存泄露 } 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]指向了這個已經(jīng)彈出的對象,因此GC是不會對其進行垃圾回收的。只有push()新元素的時候使得element[x]=newObject,才會使得以前創(chuàng)建的對象有可能被回收。應(yīng)該把上面的pop()方法改成下面的代碼就安全多了:
public Object pop(){ if(element.length==size) throws EmptyStackException(); Object o=element[--size]; elements[size]=null; //使得GC有機會回收這個對象 return o; }
總結(jié)
到此這篇關(guān)于Java內(nèi)存泄露問題的文章就介紹到這了,更多相關(guān)Java內(nèi)存泄露問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis_Generator插件的安裝以及簡單使用方法(圖解)
下面小編就為大家?guī)硪黄狹yBatis_Generator插件的安裝以及簡單使用方法(圖解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05servlet的url-pattern匹配規(guī)則詳細描述(小結(jié))
在利用servlet或Filter進行url請求的匹配時,很關(guān)鍵的一點就是匹配規(guī)則。這篇文章主要介紹了servlet的url-pattern匹配規(guī)則詳細描述(小結(jié)),非常具有實用價值,需要的朋友可以參考下2018-07-07Java Spring 控制反轉(zhuǎn)(IOC)容器詳解
這篇文章主要為大家詳細介紹了Spring控制反轉(zhuǎn)IoC入門使用的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Mybatis全局配置及映射關(guān)系的實現(xiàn)
本文主要介紹了Mybatis全局配置及映射關(guān)系的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03