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