pength

          少壯不努力,長大做I.T,現(xiàn)在買房連首付都沒有。。。。
             ::  ::  :: 聯(lián)系 ::  :: 管理

          java.lang.OutofMemoryError異常 原因之一

          Posted on 2010-03-01 09:26 彭天皓 閱讀(490) 評(píng)論(0)  編輯  收藏 所屬分類: java --ssh

          我曾經(jīng)在剛?cè)胄械臅r(shí)候做過一個(gè)小的swing程序,用到了java SE,swing,Thread等東東,當(dāng)初經(jīng)驗(yàn)少也沒有做過嚴(yán)格的性能測試,布到生產(chǎn)環(huán)境用了一段時(shí)間后發(fā)現(xiàn)那個(gè)小程序有時(shí)候會(huì)拋java.lang.OutofMemoryError異常,就是java的內(nèi)存溢出。當(dāng)時(shí)也上網(wǎng)查了不少資料,試過一些辦法,代碼也稍微做了些優(yōu)化,但是有一個(gè)問題我始終是找不到解決的方案 - 不知為什么子窗體關(guān)閉后java的垃圾回收機(jī)制無法回收其資源,因?yàn)檫@個(gè)Java程序可能要經(jīng)常開關(guān)一些子窗體,那么這些子窗體關(guān)閉后無法釋放資源就造成了Java程序OutOfMemoryError的潛在的隱患!

          最近無意間在網(wǎng)上看到了一個(gè)監(jiān)控java程序內(nèi)存使用的工具 - JProbe,馬上回想起那個(gè)有關(guān)內(nèi)存溢出的難題,于是我就下載了JProbe8.0.0希望從分析內(nèi)存入手找到我要的答案。軟件下載安裝后,在安裝目錄里詳盡的用戶指南(懂點(diǎn)軟件和英語的人很快就能上手),主要是兩個(gè)步驟:

          1.用JProbe Config工具根據(jù)提示生成J2SE或者J2EE程序的控制腳本(一個(gè).jpl文件和一個(gè).bat文件),在命令行里進(jìn)入.bat文件所在的目錄,然后執(zhí)行該批處理讓要監(jiān)控的java程序跑起來

          2.運(yùn)行JProbe Console工具,點(diǎn)擊“Attach to Session...”按鈕,彈出java程序的內(nèi)存實(shí)時(shí)監(jiān)控圖表“Runtime Summary”,我們主要是看“Data”卡片里的內(nèi)容(注意:第一次使用該軟件可能會(huì)遇到一些小問題:比如發(fā)布為jar包的程序如果運(yùn)行時(shí)會(huì)去讀配置文件,從控制腳本啟動(dòng)的話,可能會(huì)發(fā)生配置文件找不到的異常,解決辦法是:不要打jar包,直接就用文件夾發(fā)布;還有可能因?yàn)橐恍⒍拒浖木W(wǎng)絡(luò)防火墻導(dǎo)致JProbe無法連接到控制腳本的session,造成監(jiān)控圖表打不開,解決辦法是:取消防火墻對(duì)于JProbe訪問網(wǎng)絡(luò)的限制)

          實(shí)時(shí)監(jiān)控圖表“Runtime Summary”如下圖所示: 
           

          實(shí)時(shí)監(jiān)控圖表

           

          可以設(shè)置要監(jiān)視的包或者類,然后點(diǎn)擊“Refresh Runtime Data”按鈕刷新這些對(duì)象占用內(nèi)存的情況,當(dāng)你覺得某個(gè)類比較可疑的話,你可以在不斷的使用程序的過程中監(jiān)視它的生命周期,看看它是否像預(yù)期的那樣在結(jié)束了生命周期后占用的內(nèi)存就被釋放。眾所周知:java的內(nèi)存回收是自動(dòng)進(jìn)行的,無需程序員干預(yù),我們稱其為垃圾回收,這個(gè)垃圾回收可能是不定期的,就是當(dāng)程序占用內(nèi)存資源比較少的情況下可能jvm的垃圾回收頻率就比較低;反之,java程序消耗內(nèi)存資源比較多的情況下,垃圾回收的頻率和力度就比較高,這種垃圾回收的不確定性很可能會(huì)影響我們的判斷,但我們可以點(diǎn)擊JProbe監(jiān)控界面右上方的“Request a Garbage Collection”(像一個(gè)垃圾桶的圖標(biāo))按鈕來向jvm發(fā)出垃圾回收的請(qǐng)求,等幾秒后再去點(diǎn)擊“Refresh Runtime Data”,這個(gè)時(shí)候如果那個(gè)預(yù)期應(yīng)該已經(jīng)銷毀的對(duì)象的類名還是沒有從監(jiān)控界面下方的class列中消失或者其對(duì)象數(shù)量沒有減少的話(請(qǐng)多試幾次,中間可以夾雜些其他增加程序內(nèi)存使用的操作以確保jvm確實(shí)執(zhí)行了垃圾回收),那恭喜你!90%的可能性你已經(jīng)找到了程序的某個(gè)缺陷

          這個(gè)查找元兇的過程可能是相當(dāng)耗時(shí)的,是對(duì)程序員的耐心的挑戰(zhàn)。我熬了一下午一晚上,功夫不負(fù)有心人,基本上把我那個(gè)小程序的所有內(nèi)存溢出的漏洞都找到并補(bǔ)上了。事實(shí)告訴我之前那個(gè)子窗體關(guān)閉后資源無法釋放的根本原因是:子窗體雖然調(diào)用了dispose()方法,但是子窗體對(duì)象的引用一直都在:或者是被靜態(tài)HashMap引用、或者是它的內(nèi)部子線程類沒有釋放、或者是它的某個(gè)事件監(jiān)聽類沒有釋放(借用JProbe的火眼金睛一檢驗(yàn),發(fā)現(xiàn)問題真是一大堆啊!),so.我們要徹底釋放某個(gè)對(duì)象占用資源的關(guān)鍵在于找到并釋放所有對(duì)它的引用!

          下面是我解決具體問題的一些經(jīng)驗(yàn):

          程序中造成內(nèi)存溢出可能性最大的是HashMap,Hashtable等等集合類,尤其是靜態(tài)的,更是要慎之又慎!!!它們引用的對(duì)象可能你感覺已經(jīng)銷毀了,其實(shí)很可能你忘記remove鍵值,而如果這些集合對(duì)象還是靜態(tài)的掛在其他類里面,那么這個(gè)引用可能一直都在,借用JProbe測試一下,結(jié)果往往出人意料,解決辦法:徹底刪除鍵,remove、clear,如果允許最好把集合對(duì)象設(shè)為null

          對(duì)于不再使用的線程對(duì)象,如果要徹底殺了它,很多書上都推薦用join方法,我之前也是這樣做的,但后來借助JProbe工具我吃驚的發(fā)現(xiàn)這樣做很可能要?dú)⒌木€程仍舊好好的活在你日益增大的內(nèi)存里,很可能調(diào)用了線程的sleep方法后用join方法就會(huì)有點(diǎn)問題,解決辦法:在join方法前再加一句執(zhí)行interrupt方法,不過這個(gè)時(shí)候可能會(huì)有新的問題:執(zhí)行interrupt方法后你的線程類會(huì)拋InterruptedException,上有政策下有對(duì)策,加一個(gè)開關(guān)變量做判斷就能完美解決,可參考下面的代碼:

          Java代碼:
           

          1. /**    
          2.  * <p>Description: 創(chuàng)建線程的內(nèi)部類</p>    
          3.  * @author cuishen    
          4.  * @version 1.1    
          5.  */    
          6. class NewThread implements Runnable {     
          7.     Thread t;     
          8.     NewThread() {     
          9.         t = new Thread(this, path);     
          10.         t.start();     
          11.     }     
          12.     public void run() {     
          13.         try {     
          14.             while(isThreadAlive) {     
          15.                 startMonitor();     
          16.                 Thread.sleep(Long.parseLong(controlList.get(controlList.size() - 1).toString()));     
          17.             }     
          18.         } catch (InterruptedException e) {     
          19.             if(!ifForceInterruptThread) {//開關(guān)變量     
          20.                 stopThread(logThread);     
          21.                 String error = "InterruptedException!!!" + path + ": Interrupted,線程異常終止!程序已試圖重啟該線程!!";     
          22.                 System.err.println(error);     
          23.                 LogService.writeLog(error);     
          24.                 createLogThread();     
          25.             }     
          26.         }     
          27.     }     
          28. }     
          29.     
          30. public void createLogThread() {     
          31.     ifForceInterruptThread = false;//開關(guān)變量     
          32.     logThread = new NewThread();     
          33. }     
          34.     
          35. private void stopThread(NewThread thread) {     
          36.     try {     
          37.         thread.t.join(100);     
          38.     } catch (InterruptedException ex) {     
          39.         System.out.println("線程終止異常!!!");     
          40.     } finally {     
          41.         thread = null;     
          42.     }     
          43. }     
          44.     
          45. /**    
          46.  * 關(guān)閉并徹底釋放該線程資源的方法    
          47.  */    
          48. public void stopThread() {     
          49.     try {     
          50.         ifForceInterruptThread = true;//開關(guān)變量     
          51.         isThreadAlive = false;     
          52.         logThread.t.interrupt();     
          53.         logThread.t.join(50);     
          54.     } catch (InterruptedException ex) {     
          55.         System.out.println("線程終止異常!!!");     
          56.     } finally {     
          57.         this.controlList = null;     
          58.         this.keyList = null;     
          59.         logThread = null;     
          60.     }     
          61. }   

          對(duì)于繼承JFrame的窗體類,我們要注意在初始化方法中加入:this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); ,并且注意和其關(guān)聯(lián)的事件監(jiān)聽類一律寫成窗體類的內(nèi)部類,這樣窗體dispose()的時(shí)候,這些內(nèi)部類也一并銷毀,就不會(huì)再有什么莫名其妙的引用了.

          資料來源:http://developer.51cto.com/art/200906/128944.htm


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 刚察县| 南昌市| 出国| 绥宁县| 东明县| 武冈市| 神池县| 平昌县| 濮阳市| 尖扎县| 瑞金市| 呈贡县| 视频| 广灵县| 新津县| 玛多县| 永靖县| 循化| 高安市| 南漳县| 江津市| 三台县| 阳春市| 石棉县| 资阳市| 莆田市| 徐州市| 兴隆县| 大同县| 舟山市| 阿巴嘎旗| 广灵县| 德阳市| 佛教| 大理市| 二连浩特市| 吴忠市| 阿拉善右旗| 蓝田县| 南和县| 漳平市|