內(nèi)存泄漏定義(memory leak):一個(gè)不再被程序使用的對(duì)象或變量還在內(nèi)存中占有存儲(chǔ)空間。一次內(nèi)存泄漏似乎不會(huì)有大的影響,但內(nèi)存泄漏堆積后的后果就是內(nèi)存溢出。
內(nèi)存溢出 out of memory :指程序申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存供申請(qǐng)者使用,或者說(shuō),給了你一塊存儲(chǔ)int類(lèi)型數(shù)據(jù)的存儲(chǔ)空間,但是你卻存儲(chǔ)long類(lèi)型的數(shù)據(jù),那么結(jié)果就是內(nèi)存不夠用,此時(shí)就會(huì)報(bào)錯(cuò)OOM,即所謂的內(nèi)存溢出。
二者的關(guān)系:
- 內(nèi)存泄漏的堆積最終會(huì)導(dǎo)致內(nèi)存溢出
- 內(nèi)存溢出就是你要的內(nèi)存空間超過(guò)了系統(tǒng)實(shí)際分配給你的空間,此時(shí)系統(tǒng)相當(dāng)于沒(méi)法滿(mǎn)足你的需求,就會(huì)報(bào)內(nèi)存溢出的錯(cuò)誤。
- 內(nèi)存泄漏是指你向系統(tǒng)申請(qǐng)分配內(nèi)存進(jìn)行使用(new),可是使用完了以后卻不歸還(delete),結(jié)果你申請(qǐng)到的那塊內(nèi)存你自己也不能再訪(fǎng)問(wèn)(也許你把它的地址給弄丟了),而系統(tǒng)也不能再次將它分配給需要的程序。就相當(dāng)于你租了個(gè)帶鑰匙的柜子,你存完?yáng)|西之后把柜子鎖上之后,把鑰匙丟了或者沒(méi)有將鑰匙還回去,那么結(jié)果就是這個(gè)柜子將無(wú)法供給任何人使用,也無(wú)法被垃圾回收器回收,因?yàn)檎也坏剿娜魏涡畔ⅰ?/li>
- 內(nèi)存溢出:一個(gè)盤(pán)子用盡各種方法只能裝4個(gè)果子,你裝了5個(gè),結(jié)果掉倒地上不能吃了。這就是溢出。比方說(shuō)棧,棧滿(mǎn)時(shí)再做進(jìn)棧必定產(chǎn)生空間溢出,叫上溢,棧空時(shí)再做退棧也產(chǎn)生空間溢出,稱(chēng)為下溢。就是分配的內(nèi)存不足以放下數(shù)據(jù)項(xiàng)序列,稱(chēng)為內(nèi)存溢出。說(shuō)白了就是我承受不了那么多,那我就報(bào)錯(cuò),
由于JAVA的JVM引入了垃圾回收機(jī)制,垃圾回收器會(huì)自動(dòng)回收不再使用的對(duì)象,了解JVM回收機(jī)制的都知道JVM是使用引用計(jì)數(shù)法和可達(dá)性分析算法來(lái)判斷對(duì)象是否是不再使用的對(duì)象,本質(zhì)都是判斷一個(gè)對(duì)象是否還被引用。那么對(duì)于這種情況下,由于代碼的實(shí)現(xiàn)不同就會(huì)出現(xiàn)很多種內(nèi)存泄漏問(wèn)題(讓JVM誤以為此對(duì)象還在引用中,無(wú)法回收,造成內(nèi)存泄漏)。
1、靜態(tài)集合類(lèi),如HashMap、LinkedList等等。如果這些容器為靜態(tài)的,那么它們的生命周期與程序一致,則容器中的對(duì)象在程序結(jié)束之前將不能被釋放,從而造成內(nèi)存泄漏。簡(jiǎn)單而言,長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用,盡管短生命周期的對(duì)象不再使用,但是因?yàn)殚L(zhǎng)生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收。
2、各種連接,如數(shù)據(jù)庫(kù)連接、網(wǎng)絡(luò)連接和IO連接等。在對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作的過(guò)程中,首先需要建立與數(shù)據(jù)庫(kù)的連接,當(dāng)不再使用時(shí),需要調(diào)用close方法來(lái)釋放與數(shù)據(jù)庫(kù)的連接。只有連接被關(guān)閉后,垃圾回收器才會(huì)回收對(duì)應(yīng)的對(duì)象。否則,如果在訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的過(guò)程中,對(duì)Connection、Statement或ResultSet不顯性地關(guān)閉,將會(huì)造成大量的對(duì)象無(wú)法被回收,從而引起內(nèi)存泄漏。
3、變量不合理的作用域。一般而言,一個(gè)變量的定義的作用范圍大于其使用范圍,很有可能會(huì)造成內(nèi)存泄漏。另一方面,如果沒(méi)有及時(shí)地把對(duì)象設(shè)置為null,很有可能導(dǎo)致內(nèi)存泄漏的發(fā)生。
public class UsingRandom { private String msg; public void receiveMsg(){ readFromNet();// 從網(wǎng)絡(luò)中接受數(shù)據(jù)保存到msg中 saveDB();// 把msg保存到數(shù)據(jù)庫(kù)中 } }
如上面這個(gè)偽代碼,通過(guò)readFromNet方法把接受的消息保存在變量msg中,然后調(diào)用saveDB方法把msg的內(nèi)容保存到數(shù)據(jù)庫(kù)中,此時(shí)msg已經(jīng)就沒(méi)用了,由于msg的生命周期與對(duì)象的生命周期相同,此時(shí)msg還不能回收,因此造成了內(nèi)存泄漏。
實(shí)際上這個(gè)msg變量可以放在receiveMsg方法內(nèi)部,當(dāng)方法使用完,那么msg的生命周期也就結(jié)束,此時(shí)就可以回收了。還有一種方法,在使用完msg后,把msg設(shè)置為null,這樣垃圾回收器也會(huì)回收msg的內(nèi)存空間。
4、內(nèi)部類(lèi)持有外部類(lèi),如果一個(gè)外部類(lèi)的實(shí)例對(duì)象的方法返回了一個(gè)內(nèi)部類(lèi)的實(shí)例對(duì)象,這個(gè)內(nèi)部類(lèi)對(duì)象被長(zhǎng)期引用了,即使那個(gè)外部類(lèi)實(shí)例對(duì)象不再被使用,但由于內(nèi)部類(lèi)持有外部類(lèi)的實(shí)例對(duì)象,這個(gè)外部類(lèi)對(duì)象將不會(huì)被垃圾回收,這也會(huì)造成內(nèi)存泄露。
5、改變哈希值,當(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)存泄露。