淺談Java編程中的內(nèi)存泄露情況
必須先要了解的
1。c/c++是程序員自己管理內(nèi)存,Java內(nèi)存是由GC自動(dòng)回收的。
我雖然不是很熟悉C++,不過這個(gè)應(yīng)該沒有犯常識(shí)性錯(cuò)誤吧。
2。什么是內(nèi)存泄露?
內(nèi)存泄露是指系統(tǒng)中存在無法回收的內(nèi)存,有時(shí)候會(huì)造成內(nèi)存不足或系統(tǒng)崩潰。
在C/C++中分配了內(nèi)存不釋放的情況就是內(nèi)存泄露。
3。Java存在內(nèi)存泄露
我們必須先承認(rèn)這個(gè),才可以接著討論。雖然Java存在內(nèi)存泄露,但是基本上不用很關(guān)心它,特別是那些對(duì)代碼本身就不講究的就更不要去關(guān)心這個(gè)了。
Java中的內(nèi)存泄露當(dāng)然是指:存在無用但是垃圾回收器無法回收的對(duì)象。
而且即使有內(nèi)存泄露問題存在,也不一定會(huì)表現(xiàn)出來。
4。Java中參數(shù)都是傳值的。
對(duì)于基本類型,大家基本上沒有異議,但是對(duì)于引用類型我們也不能有異議。
Java內(nèi)存泄露情況
1、堆內(nèi)存溢出(outOfMemoryError:java heap space)
在jvm規(guī)范中,堆中的內(nèi)存是用來生成對(duì)象實(shí)例和數(shù)組的。
如果細(xì)分,堆內(nèi)存還可以分為年輕代和年老代,年輕代包括一個(gè)eden區(qū)和兩個(gè)survivor區(qū)。
當(dāng)生成新對(duì)象時(shí),內(nèi)存的申請(qǐng)過程如下:
a、jvm先嘗試在eden區(qū)分配新建對(duì)象所需的內(nèi)存;
b、如果內(nèi)存大小足夠,申請(qǐng)結(jié)束,否則下一步;
c、jvm啟動(dòng)youngGC,試圖將eden區(qū)中不活躍的對(duì)象釋放掉,釋放后若Eden空間仍然不足以放入新對(duì)象,則試圖將部分Eden中活躍對(duì)象放入Survivor區(qū);
d、Survivor區(qū)被用來作為Eden及old的中間交換區(qū)域,當(dāng)OLD區(qū)空間足夠時(shí),Survivor區(qū)的對(duì)象會(huì)被移到Old區(qū),否則會(huì)被保留在Survivor區(qū);
e、 當(dāng)OLD區(qū)空間不夠時(shí),JVM會(huì)在OLD區(qū)進(jìn)行full GC;
f、full GC后,若Survivor及OLD區(qū)仍然無法存放從Eden復(fù)制過來的部分對(duì)象,導(dǎo)致JVM無法在Eden區(qū)為新對(duì)象創(chuàng)建內(nèi)存區(qū)域,則出現(xiàn)”out of memory錯(cuò)誤”:
outOfMemoryError:java heap space
2、方法區(qū)內(nèi)存溢出(outOfMemoryError:permgem space)
在jvm規(guī)范中,方法區(qū)主要存放的是類信息、常量、靜態(tài)變量等。
所以如果程序加載的類過多,或者使用反射、gclib等這種動(dòng)態(tài)代理生成類的技術(shù),就可能導(dǎo)致該區(qū)發(fā)生內(nèi)存溢出,一般該區(qū)發(fā)生內(nèi)存溢出時(shí)的錯(cuò)誤信息為:
outOfMemoryError:permgem space
3、線程棧溢出(java。lang。StackOverflowError)
線程棧時(shí)線程獨(dú)有的一塊內(nèi)存結(jié)構(gòu),所以線程棧發(fā)生問題必定是某個(gè)線程運(yùn)行時(shí)產(chǎn)生的錯(cuò)誤。
一般線程棧溢出是由于遞歸太深或方法調(diào)用層級(jí)過多導(dǎo)致的。
發(fā)生棧溢出的錯(cuò)誤信息為:
java。lang。StackOverflowError
內(nèi)存泄露的幾種場(chǎng)景:
1、長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用
這是內(nèi)存泄露最常見的場(chǎng)景,也是代碼設(shè)計(jì)中經(jīng)常出現(xiàn)的問題。
例如:在全局靜態(tài)map中緩存局部變量,且沒有清空操作,隨著時(shí)間的推移,這個(gè)map會(huì)越來越大,造成內(nèi)存泄露。
2、修改hashset中對(duì)象的參數(shù)值,且參數(shù)是計(jì)算哈希值的字段
當(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)致無法從HashSet集合中刪除當(dāng)前對(duì)象,造成內(nèi)存泄露。
3、機(jī)器的連接數(shù)和關(guān)閉時(shí)間設(shè)置
長(zhǎng)時(shí)間開啟非常耗費(fèi)資源的連接,也會(huì)造成內(nèi)存泄露。
來看個(gè)內(nè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)單,假如堆棧加了10個(gè)元素,然后全部彈出來,雖然堆棧
是空的,沒有我們要的東西,但是這是個(gè)對(duì)象是無法回收的,這個(gè)才符合了內(nèi)存
泄露的兩個(gè)條件:無用,無法回收。
但是就是存在這樣的東西也不一定會(huì)導(dǎo)致什么樣的后果,如果這個(gè)堆棧用的比較少,
也就浪費(fèi)了幾個(gè)K內(nèi)存而已,反正我們的內(nèi)存都上G了,哪里會(huì)有什么影響,再說
這個(gè)東西很快就會(huì)被回收的,有什么關(guān)系。下面看兩個(gè)例子。
例子1
public class Bad{ public static Stack s=Stack(); static{ s。push(new Object()); s。pop(); //這里有一個(gè)對(duì)象發(fā)生內(nèi)存泄露 s。push(new Object()); //上面的對(duì)象可以被回收了,等于是自愈了 } }
因?yàn)槭莝tatic,就一直存在到程序退出,但是我們也可以看到它有自愈功能,
就是說如果你的Stack最多有100個(gè)對(duì)象,那么最多也就只有100個(gè)對(duì)象無法被回收
其實(shí)這個(gè)應(yīng)該很容易理解,Stack內(nèi)部持有100個(gè)引用,最壞的情況就是他們都是
無用的,因?yàn)槲覀円坏┓判碌倪M(jìn)取,以前的引用自然消失!
例子2
public class NotTooBad{ public void doSomething(){ Stack s=new Stack(); s。push(new Object()); //other code s。pop();//這里同樣導(dǎo)致對(duì)象無法回收,內(nèi)存泄露。 }//退出方法,s自動(dòng)無效,s可以被回收,Stack內(nèi)部的引用自然沒了,所以 //這里也可以自愈,而且可以說這個(gè)方法不存在內(nèi)存泄露問題,不過是晚一點(diǎn) //交給GC而已,因?yàn)樗欠忾]的,對(duì)外不開放,可以說上面的代碼99。9999%的 //情況是不會(huì)造成任何影響的,當(dāng)然你寫這樣的代碼不會(huì)有什么壞的影響,但是 //絕對(duì)可以說是垃圾代碼!沒有矛盾吧,我在里面加一個(gè)空的for循環(huán)也不會(huì)有 //什么太大的影響吧,你會(huì)這么做嗎? }
上面兩個(gè)例子都不過是小打小鬧,但是C/C++中的內(nèi)存泄露就不是Bad了,而是Worst了。
他們?nèi)绻惶帥]有回收就永遠(yuǎn)無法回收,頻繁的調(diào)用這個(gè)方法內(nèi)存不就用光了!
因?yàn)镴ava還有自愈功能(我自己起的名字,還沒申請(qǐng)專利),所以Java的內(nèi)存泄露問題
幾乎可以忽略了,但是知道的人就不要犯了。
為了避免內(nèi)存泄露,在編寫代碼的過程中可以參考下面的建議:
1、盡早釋放無用對(duì)象的引用;
2、使用字符串處理,避免使用String,應(yīng)大量使用StringBuffer,每一個(gè)String對(duì)象都得獨(dú)立占用內(nèi)存一塊區(qū)域;
3、盡量少用靜態(tài)變量,因?yàn)殪o態(tài)變量存放在永久代(方法區(qū)),永久代基本不參與垃圾回收;
4、避免在循環(huán)中創(chuàng)建對(duì)象;
5、開啟大型文件或從數(shù)據(jù)庫(kù)一次拿了太多的數(shù)據(jù)很容易造成內(nèi)存溢出,所以在這些地方要大概計(jì)算一下數(shù)據(jù)量的最大值是多少,并且設(shè)定所需最小及最大的內(nèi)存空間值。
相關(guān)文章
SpringBoot參數(shù)校驗(yàn)之@Validated的使用詳解
這篇文章主要通過示例為大家詳細(xì)介紹一下介紹了SpringBoot參數(shù)校驗(yàn)中@Validated的使用方法,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-06-06詳解Springboot快速搭建跨域API接口的步驟(idea社區(qū)版2023.1.4+apache-maven-3.9.
這篇文章主要介紹了Springboot快速搭建跨域API接口(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07Java Web Listener實(shí)現(xiàn)事件監(jiān)聽與處理
Java Web開發(fā)中的Listener是一種事件機(jī)制,通過監(jiān)聽Web應(yīng)用程序的事件,實(shí)現(xiàn)對(duì)事件的處理,從而實(shí)現(xiàn)更加靈活和高效的應(yīng)用程序開發(fā)。Listener能夠監(jiān)聽的事件包括應(yīng)用程序啟動(dòng)和關(guān)閉、Session創(chuàng)建和銷毀、請(qǐng)求和響應(yīng)對(duì)象的創(chuàng)建和銷毀等2023-04-04Java代碼統(tǒng)計(jì)網(wǎng)站中不同省份用戶的訪問數(shù)
這篇文章主要介紹了Java代碼統(tǒng)計(jì)網(wǎng)站中不同省份用戶的訪問數(shù) 的相關(guān)資料,非常具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-05-05