一、內存溢出的定義和原因
定義
內(nei)(nei)(nei)存(cun)(cun)溢(yi)出是(shi)指(zhi)應(ying)用(yong)系(xi)統(tong)中(zhong)(zhong)存(cun)(cun)在(zai)無法(fa)回(hui)(hui)(hui)收的(de)(de)(de)內(nei)(nei)(nei)存(cun)(cun)或使(shi)用(yong)的(de)(de)(de)內(nei)(nei)(nei)存(cun)(cun)過(guo)多,最終使(shi)得(de)程(cheng)序運行(xing)(xing)(xing)要(yao)用(yong)到的(de)(de)(de)內(nei)(nei)(nei)存(cun)(cun)大于虛(xu)擬機能提供的(de)(de)(de)最大內(nei)(nei)(nei)存(cun)(cun)。為了(le)解決Java中(zhong)(zhong)內(nei)(nei)(nei)存(cun)(cun)溢(yi)出問(wen)題,我們首先必須了(le)解Java是(shi)如何管(guan)理(li)內(nei)(nei)(nei)存(cun)(cun)的(de)(de)(de)。Java的(de)(de)(de)內(nei)(nei)(nei)存(cun)(cun)管(guan)理(li)就是(shi)對象的(de)(de)(de)分配和釋放(fang)問(wen)題。在(zai)Java中(zhong)(zhong),內(nei)(nei)(nei)存(cun)(cun)的(de)(de)(de)分配是(shi)由(you)程(cheng)序完(wan)成(cheng)的(de)(de)(de),而內(nei)(nei)(nei)存(cun)(cun)的(de)(de)(de)釋放(fang)是(shi)由(you)垃圾收集器(GarbageCollection,GC)完(wan)成(cheng)的(de)(de)(de),程(cheng)序員不需要(yao)通過(guo)調用(yong)GC函數來(lai)釋放(fang)內(nei)(nei)(nei)存(cun)(cun),因(yin)為不同的(de)(de)(de)JVM實現(xian)者可能使(shi)用(yong)不同的(de)(de)(de)算法(fa)管(guan)理(li)GC,有(you)的(de)(de)(de)是(shi)內(nei)(nei)(nei)存(cun)(cun)使(shi)用(yong)到達(da)一定(ding)程(cheng)度時,GC才(cai)開始(shi)工作,也有(you)定(ding)時執行(xing)(xing)(xing)的(de)(de)(de),有(you)的(de)(de)(de)是(shi)中(zhong)(zhong)斷(duan)式執行(xing)(xing)(xing)GC。但GC只能回(hui)(hui)(hui)收無用(yong)并且不再被其它(ta)對象引(yin)(yin)用(yong)的(de)(de)(de)那些對象所占用(yong)的(de)(de)(de)空間。Java的(de)(de)(de)內(nei)(nei)(nei)存(cun)(cun)垃圾回(hui)(hui)(hui)收機制是(shi)從(cong)程(cheng)序的(de)(de)(de)主要(yao)運行(xing)(xing)(xing)對象開始(shi)檢查引(yin)(yin)用(yong)鏈(lian),當遍(bian)歷一遍(bian)后(hou)發(fa)現(xian)沒有(you)被引(yin)(yin)用(yong)的(de)(de)(de)孤(gu)立對象就作為垃圾回(hui)(hui)(hui)收。
原因
1、內存中加載的數據量(liang)過(guo)于(yu)龐大,如(ru)一次(ci)從數據庫取出過(guo)多數據。
2、集合類中有(you)對(dui)對(dui)象的引用(yong),使(shi)用(yong)完后(hou)未清空,使(shi)得JVM不能回(hui)收(shou)。
3、代碼中存在死循(xun)(xun)環(huan)或循(xun)(xun)環(huan)產生過多重復的對象實體。
4、使用的(de)第三方軟件(jian)中的(de)BUG。
5、啟動參(can)數(shu)設(she)定(ding)的過小。
二、內存溢出的解決問題
第一步,就(jiu)是修(xiu)改(gai)JVM啟動(dong)參數(shu),直接增加內存。這一點看上去似乎很(hen)簡單,但很(hen)容易被忽(hu)略。JVM默認可以(yi)使(shi)(shi)用的(de)內存為(wei)64M,Tomcat默認可以(yi)使(shi)(shi)用的(de)內存為(wei)128MB,對于稍復(fu)雜一點的(de)系統就(jiu)會不夠(gou)用。在某項目(mu)中,就(jiu)因(yin)為(wei)啟動(dong)參數(shu)使(shi)(shi)用的(de)默認值,經常報“OutOfMemory”錯(cuo)誤。因(yin)此(ci),-Xms,-Xmx參數(shu)一定不要忘(wang)記加。
第二(er)步(bu),檢查(cha)錯誤(wu)日(ri)(ri)志,查(cha)看(kan)“OutOfMemory”錯誤(wu)前是否有其它異常或錯誤(wu)。在(zai)一(yi)個(ge)項(xiang)目(mu)中(zhong),使(shi)用(yong)兩個(ge)數(shu)據(ju)庫(ku)連(lian)(lian)接(jie),其中(zhong)專(zhuan)用(yong)于(yu)發(fa)送短信的數(shu)據(ju)庫(ku)連(lian)(lian)接(jie)使(shi)用(yong)DBCP連(lian)(lian)接(jie)池管(guan)理,用(yong)戶為不(bu)將短信發(fa)出(chu)(chu),有意將數(shu)據(ju)庫(ku)連(lian)(lian)接(jie)用(yong)戶名(ming)改(gai)錯,使(shi)得日(ri)(ri)志中(zhong)有許(xu)多數(shu)據(ju)庫(ku)連(lian)(lian)接(jie)異常的日(ri)(ri)志,一(yi)段時間后,就出(chu)(chu)現(xian)“OutOfMemory”錯誤(wu)。經分析(xi),這(zhe)是由(you)于(yu)DBCP連(lian)(lian)接(jie)池BUG引起的,數(shu)據(ju)庫(ku)連(lian)(lian)接(jie)不(bu)上后,沒有將連(lian)(lian)接(jie)釋放,最(zui)終使(shi)得DBCP報“OutOfMemory”錯誤(wu)。經過修改(gai)正確數(shu)據(ju)庫(ku)連(lian)(lian)接(jie)參數(shu)后,就沒有再出(chu)(chu)現(xian)內存溢(yi)出(chu)(chu)的錯誤(wu)。
查看日(ri)志對于(yu)分(fen)(fen)析(xi)內存(cun)溢(yi)出(chu)是非常(chang)重要的,通過仔細查看日(ri)志,分(fen)(fen)析(xi)內存(cun)溢(yi)出(chu)前(qian)做過哪些操作,可以(yi)大致定位有問題的模塊。
第三(san)步(bu),安排有經驗的編程(cheng)人(ren)員對代碼進行走(zou)查(cha)和分析,找出可能發生內(nei)存溢出的位置。重點(dian)排查(cha)以下幾點(dian):
1、檢(jian)查代碼(ma)中是否(fou)有死(si)循環或遞歸調用。
2、檢查是(shi)否(fou)有大循環(huan)重(zhong)復(fu)產生新對象實體。
3、檢查(cha)對數(shu)(shu)據(ju)庫(ku)查(cha)詢(xun)中(zhong),是否有一次(ci)獲得(de)全部數(shu)(shu)據(ju)的查(cha)詢(xun)。一般來(lai)說,如果一次(ci)取十萬條記錄到內(nei)(nei)存,就可(ke)能引(yin)起(qi)內(nei)(nei)存溢出(chu)。這個問題(ti)比(bi)較(jiao)(jiao)隱蔽(bi),在上線前,數(shu)(shu)據(ju)庫(ku)中(zhong)數(shu)(shu)據(ju)較(jiao)(jiao)少,不(bu)容易(yi)出(chu)問題(ti),上線后,數(shu)(shu)據(ju)庫(ku)中(zhong)數(shu)(shu)據(ju)多了,一次(ci)查(cha)詢(xun)就有可(ke)能引(yin)起(qi)內(nei)(nei)存溢出(chu)。因此對于數(shu)(shu)據(ju)庫(ku)查(cha)詢(xun)盡量(liang)采用分頁的方式查(cha)詢(xun)。
4、檢(jian)查List、MAP等集合對象是(shi)否有使(shi)用(yong)完后,未清(qing)除的(de)問題。List、MAP等集合對象會始終(zhong)存有對對象的(de)引用(yong),使(shi)得(de)這(zhe)些對象不能被GC回(hui)收。
第(di)四步,使(shi)用(yong)內(nei)存(cun)(cun)(cun)查看(kan)工(gong)具(ju)動(dong)態查看(kan)內(nei)存(cun)(cun)(cun)使(shi)用(yong)情(qing)況。某個(ge)項目上線后,每(mei)次系統啟動(dong)兩(liang)天后,就(jiu)(jiu)會出現(xian)內(nei)存(cun)(cun)(cun)溢(yi)出的錯誤(wu)。這種情(qing)況一般(ban)是(shi)代碼中(zhong)出現(xian)了緩慢的內(nei)存(cun)(cun)(cun)泄(xie)漏,用(yong)上面三(san)個(ge)步驟(zou)解決不(bu)了,這就(jiu)(jiu)需(xu)要使(shi)用(yong)內(nei)存(cun)(cun)(cun)查看(kan)工(gong)具(ju)了。
內存查看工具有許多,比較有名的有:Optimizeit Profiler、JProbeProfiler、JinSight和Java1.5的Jconsole等。它們的基本工作原理大同小異,都是監測Java程序運行時所有對象的申請、釋放等動作,將內存管理的所有信息進行統計、分(fen)(fen)析(xi)(xi)、可(ke)視化。開發人(ren)員(yuan)可(ke)以(yi)根據這些信息判斷程序(xu)是否(fou)有內(nei)存(cun)泄漏問題。一(yi)(yi)般來(lai)說,一(yi)(yi)個(ge)正常的(de)系統在其啟(qi)動完成(cheng)后其內(nei)存(cun)的(de)占(zhan)用(yong)量是基(ji)本穩定的(de),而不應該是無限制的(de)增(zeng)長的(de)。持續(xu)地觀察系統運(yun)行時使用(yong)的(de)內(nei)存(cun)的(de)大(da)小,可(ke)以(yi)看到(dao)在內(nei)存(cun)使用(yong)監控窗口中(zhong)是基(ji)本規則的(de)鋸(ju)齒形的(de)圖線,如(ru)果內(nei)存(cun)的(de)大(da)小持續(xu)地增(zeng)長,則說明系統存(cun)在內(nei)存(cun)泄漏問題。通(tong)過間隔一(yi)(yi)段時間取一(yi)(yi)次內(nei)存(cun)快照,然后對內(nei)存(cun)快照中(zhong)對象的(de)使用(yong)與(yu)引用(yong)等信息進行比對與(yu)分(fen)(fen)析(xi)(xi),可(ke)以(yi)找出是哪個(ge)類(lei)的(de)對象在泄漏。
通(tong)過以(yi)上(shang)四個(ge)步驟的(de)分析與(yu)(yu)處(chu)理,基本能處(chu)理內存溢出的(de)問題。當然,在這些過程中也需(xu)要相當的(de)經驗與(yu)(yu)敏感(gan)度,需(xu)要在實際的(de)開(kai)發與(yu)(yu)調試過程中不斷積累。
申明:以上方法源于程序系統索引或網民分享提供,僅供您參考使用,不代表本網站的研究觀點,證明有效,請注意甄別內容來源的真實性和權威性。