应用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap space)
http://www.educity.cn/wenda/351088.html
?
使用jacob組件造成的內(nèi)存溢出解決方案(java.lang.OutOfMemoryError: Java heap space)
都說(shuō)內(nèi)存泄漏是C++的通病,內(nèi)存溢出是Java的硬傷,這個(gè)頭疼的問(wèn)題算是讓我給碰到了。我在做的這個(gè)功能涉及到修改word文檔,因?yàn)槲④洓](méi)有公開(kāi)word源代碼,所以直接用java流來(lái)讀取word的后果是讀出來(lái)的會(huì)是亂碼,經(jīng)過(guò)查資料得知可以使用poi和jacob來(lái)操作word,jacob使用起來(lái)相對(duì)poi要方便很多,因此我選擇了jacob,Jacob 是Java-COM Bridge的縮寫(xiě),它在Java與微軟的COM組件之間構(gòu)建一座橋梁。使用Jacob自帶的DLL動(dòng)態(tài)鏈接庫(kù),并通過(guò)JNI(Java Native Interface Java本地調(diào)用)的方式實(shí)現(xiàn)了在Java平臺(tái)上對(duì)COM程序的調(diào)用。因?yàn)閐ll文件不能在linux上運(yùn)行,而客戶端只和linux交互,所以還需要一個(gè)windows服務(wù)器,這兩個(gè)服務(wù)器不斷的互相下載word,下載的頻繁度最高連續(xù)達(dá)到十萬(wàn)次,以下是服務(wù)器之間的交互圖:
當(dāng)功能實(shí)現(xiàn)了之后進(jìn)行了一下測(cè)試,結(jié)果內(nèi)存溢出了,于是就開(kāi)始連查帶改弄了半個(gè)月,檢查打開(kāi)的流有沒(méi)有關(guān)閉,有沒(méi)有大量使用靜態(tài)變量,有沒(méi)有大量使用String進(jìn)行字符串拼接,遺憾的是沒(méi)有找出問(wèn)題在哪里(說(shuō)明我寫(xiě)的代碼質(zhì)量還是不錯(cuò)的),也試圖增加jvm內(nèi)存,但增加jvm內(nèi)存只能治標(biāo)而不能治本,不是可靠的辦法,經(jīng)過(guò)大量查閱資料,得知com的線程回收不由java垃圾回收器進(jìn)行處理,因此,每new一次jacob提供的類就要分配一定大小的內(nèi)存給該操作,new出來(lái)的這個(gè)com對(duì)象在使用結(jié)束之后產(chǎn)生的垃圾java是無(wú)法回收的,new出來(lái)的對(duì)象越來(lái)越多,內(nèi)存溢出就不可避免了,即使增加jvm內(nèi)存也只是暫時(shí)的,遲早這些對(duì)象會(huì)把內(nèi)存用完。既然java不能回收這些垃圾,那么com組件也應(yīng)該提供了回收垃圾的方法,最后得知是ComThread.InitSTA()和ComThread.Release()方法,這兩個(gè)方法其實(shí)就是初始化一個(gè)線程和結(jié)束這個(gè)線程,在創(chuàng)建com對(duì)象的時(shí)候初始化一個(gè)線程來(lái)運(yùn)行這個(gè)對(duì)象,這個(gè)對(duì)象使用結(jié)束之后再結(jié)束線程,問(wèn)題就這樣得到解決了,程序連續(xù)運(yùn)行一兩天內(nèi)存一直很平穩(wěn),弄了快一個(gè)月的問(wèn)題終于解決了,以下是全部代碼:
/** * @fileName MSWordManager.java * @description 該類用于查找word文檔指定位置并將圖片插入 * @date 2011-10-21 * @time * @author wst */ public class MSWordManager { private Logger log = Logger.getLogger(MSWordManager.class); // word文檔 private Dispatch doc; // word運(yùn)行程序?qū)ο?private ActiveXComponent word; // 所有word文檔集合 private Dispatch documents; // 選定的范圍或插入點(diǎn) private Dispatch selection; public static int instanceSize=3;//一個(gè)線程存放的MSWordManager數(shù)量 public MSWordManager(int index) { if (word == null) { word = new ActiveXComponent("Word.Application"); //為true表示word應(yīng)用程序可見(jiàn) word.setProperty("Visible", new Variant(false)); } if (documents == null){ documents = word.getProperty("Documents").toDispatch(); } if(index==0){ ComThread.InitSTA();//初始化一個(gè)線程并放入內(nèi)存中等待調(diào)用 } } /** * 打開(kāi)一個(gè)已經(jīng)存在的文檔 * @param docPath 要打開(kāi)的文檔 * @param key 文本框的內(nèi)容,根據(jù)該key獲取文本框當(dāng)前位置 * @date 2011-12-9 * @author wst */ public void openDocumentAndGetSelection(String docPath, String key) { try{ closeDocument() // 打開(kāi)文檔 doc = Dispatch.call(documents, "Open", docPath).toDispatch(); // shapes集合 Dispatch shapes = Dispatch.get(doc, "Shapes").toDispatch(); // shape的個(gè)數(shù) String Count = Dispatch.get(shapes, "Count").toString(); for (int i = 1; i <= Integer.parseInt(Count); i++) { // 取得一個(gè)shape Dispatch shape = Dispatch.call(shapes, "Item", new Variant(i)).toDispatch(); // 從一個(gè)shape里面獲取到文本框 Dispatch textframe = Dispatch.get(shape, "TextFrame").toDispatch(); boolean hasText = Dispatch.call(textframe, "HasText").toBoolean(); if (hasText) { // 獲取該文本框?qū)ο?Dispatch TextRange = Dispatch.get(textframe, "TextRange").toDispatch(); // 獲取文本框中的字符串 String str = Dispatch.get(TextRange, "Text").toString(); //獲取指定字符key所在的文本框的位置 if (str != null && !str.equals("") && str.indexOf(key) > -1) { //當(dāng)前文本框的位置 selection = Dispatch.get(textframe, "TextRange").toDispatch(); // 情況文本框內(nèi)容 Dispatch.put(selection, "Text", ""); break; } } } }catch(Exception e){ log.error(e); return; } } /** * 在當(dāng)前位置插入圖片 * @param imagePath 產(chǎn)生圖片的路徑 * @return 成功:true;失敗:false */ public boolean insertImage(String imagePath) { try{ Dispatch.call(Dispatch.get(selection, "InLineShapes").toDispatch(),"AddPicture", imagePath); }catch(Exception e){ log.error(e); return false; } return true; } //關(guān)閉文檔 public void closeDocument() { if (doc != null) { Dispatch.call(doc, "Close"); doc = null; } } //關(guān)閉全部應(yīng)用 public void close(int index) { if (word != null) { Dispatch.call(word, "Quit"); word = null; } selection = null; documents = null; if(index==instanceSize){ //釋放占用的內(nèi)存空間,因?yàn)閏om的線程回收不由java的垃圾回收器處理 ComThread.Release(); } } }
問(wèn)題解決了,雖然寫(xiě)的java程序沒(méi)有什么問(wèn)題,但是也學(xué)習(xí)到了一些如何防止內(nèi)存溢出的知識(shí),下面來(lái)看看我在網(wǎng)絡(luò)找到的幾種常見(jiàn)的內(nèi)存溢出以及如何檢測(cè)出內(nèi)存溢出和出來(lái)辦法。
一、幾種典型的內(nèi)存泄漏
我們知道了在Java中確實(shí)會(huì)存在內(nèi)存泄漏,那么就讓我們看一看幾種典型的泄漏,并找出他們發(fā)生的原因和解決方法。?
1?全局集合?
在大型應(yīng)用程序中存在各種各樣的全局?jǐn)?shù)據(jù)倉(cāng)庫(kù)是很普遍的,比如一個(gè)JNDI-tree或者一個(gè)session table。在這些情況下,必須注意管理儲(chǔ)存庫(kù)的大小。必須有某種機(jī)制從儲(chǔ)存庫(kù)中移除不再需要的數(shù)據(jù)。?
通常有很多不同的解決形式,其中最常用的是一種周期運(yùn)行的清除作業(yè)。這個(gè)作業(yè)會(huì)驗(yàn)證倉(cāng)庫(kù)中的數(shù)據(jù)然后清除一切不需要的數(shù)據(jù)。?
另一種管理儲(chǔ)存庫(kù)的方法是使用反向鏈接(referrer)計(jì)數(shù)。然后集合負(fù)責(zé)統(tǒng)計(jì)集合中每個(gè)入口的反向鏈接的數(shù)目。這要求反向鏈接告訴集合何時(shí)會(huì)退出入口。當(dāng)反向鏈接數(shù)目為零時(shí),該元素就可以從集合中移除了。?
2?緩存?
緩存一種用來(lái)快速查找已經(jīng)執(zhí)行過(guò)的操作結(jié)果的數(shù)據(jù)結(jié)構(gòu)。因此,如果一個(gè)操作執(zhí)行需要比較多的資源并會(huì)多次被使用,通常做法是把常用的輸入數(shù)據(jù)的操作結(jié)果進(jìn)行緩存,以便在下次調(diào)用該操作時(shí)使用緩存的數(shù)據(jù)。緩存通常都是以動(dòng)態(tài)方式實(shí)現(xiàn)的,如果緩存設(shè)置不正確而大量使用緩存的話則會(huì)出現(xiàn)內(nèi)存溢出的后果,因此需要將所使用的內(nèi)存容量與檢索數(shù)據(jù)的速度加以平衡。?
常用的解決途徑是使用java.lang.ref.SoftReference類堅(jiān)持將對(duì)象放入緩存。這個(gè)方法可以保證當(dāng)虛擬機(jī)用完內(nèi)存或者需要更多堆的時(shí)候,可以釋放這些對(duì)象的引用。?
3?類裝載器?
Java類裝載器的使用為內(nèi)存泄漏提供了許多可乘之機(jī)。一般來(lái)說(shuō)類裝載器都具有復(fù)雜結(jié)構(gòu),因?yàn)轭愌b載器不僅僅是只與"常規(guī)"對(duì)象引用有關(guān),同時(shí)也和對(duì)象內(nèi)部的引用有關(guān)。比如數(shù)據(jù)變量,方法和各種類。這意味著只要存在對(duì)數(shù)據(jù)變量,方法,各種類和對(duì)象的類裝載器,那么類裝載器將駐留在JVM中。既然類裝載器可以同很多的類關(guān)聯(lián),同時(shí)也可以和靜態(tài)數(shù)據(jù)變量關(guān)聯(lián),那么相當(dāng)多的內(nèi)存就可能發(fā)生泄漏。
二、如何檢測(cè)和處理內(nèi)存泄漏?
如何查找引起內(nèi)存泄漏的原因一般有兩個(gè)步驟:第一是安排有經(jīng)驗(yàn)的編程人員對(duì)代碼進(jìn)行走查和分析,找出內(nèi)存泄漏發(fā)生的位置;第二是使用專門(mén)的內(nèi)存泄漏測(cè)試工具進(jìn)行測(cè)試。?
第一個(gè)步驟在代碼走查的工作中,可以安排對(duì)系統(tǒng)業(yè)務(wù)和開(kāi)發(fā)語(yǔ)言工具比較熟悉的開(kāi)發(fā)人員對(duì)應(yīng)用的代碼進(jìn)行了交叉走查,盡量找出代碼中存在的數(shù)據(jù)庫(kù)連接聲明和結(jié)果集未關(guān)閉、代碼冗余等故障代碼。?
第二個(gè)步驟就是檢測(cè)Java的內(nèi)存泄漏。在這里我們通常使用一些工具來(lái)檢查Java程序的內(nèi)存泄漏問(wèn)題。市場(chǎng)上已有幾種專業(yè)檢查Java內(nèi)存泄漏的工具,它們的基本工作原理大同小異,都是通過(guò)監(jiān)測(cè)Java程序運(yùn)行時(shí),所有對(duì)象的申請(qǐng)、釋放等動(dòng)作,將內(nèi)存管理的所有信息進(jìn)行統(tǒng)計(jì)、分析、可視化。開(kāi)發(fā)人員將根據(jù)這些信息判斷程序是否有內(nèi)存泄漏問(wèn)題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。?
1?檢測(cè)內(nèi)存泄漏的存在
這里我們將簡(jiǎn)單介紹我們?cè)谑褂肙ptimizeit檢查的過(guò)程。通常在知道發(fā)生內(nèi)存泄漏之后,第一步是要弄清楚泄漏了什么數(shù)據(jù)和哪個(gè)類的對(duì)象引起了泄漏。?
一般說(shuō)來(lái),一個(gè)正常的系統(tǒng)在其運(yùn)行穩(wěn)定后其內(nèi)存的占用量是基本穩(wěn)定的,不應(yīng)該是無(wú)限制的增長(zhǎng)的。同樣,對(duì)任何一個(gè)類的對(duì)象的使用個(gè)數(shù)也有一個(gè)相對(duì)穩(wěn)定的上限,不應(yīng)該是持續(xù)增長(zhǎng)的。根據(jù)這樣的基本假設(shè),我們持續(xù)地觀察系統(tǒng)運(yùn)行時(shí)使用的內(nèi)存的大小和各實(shí)例的個(gè)數(shù),如果內(nèi)存的大小持續(xù)地增長(zhǎng),則說(shuō)明系統(tǒng)存在內(nèi)存泄漏,如果特定類的實(shí)例對(duì)象個(gè)數(shù)隨時(shí)間而增長(zhǎng)(就是所謂的“增長(zhǎng)率”),則說(shuō)明這個(gè)類的實(shí)例可能存在泄漏情況。?
另一方面通常發(fā)生內(nèi)存泄漏的第一個(gè)跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError。在這種情況下,需要使用一些開(kāi)銷較低的工具來(lái)監(jiān)控和查找內(nèi)存泄漏。雖然OutOfMemoryError也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對(duì)于這種情況則可以增加JVM可用的堆的數(shù)量,或者對(duì)應(yīng)用程序進(jìn)行某種更改,使它使用較少的內(nèi)存。?
但是,在許多情況下,OutOfMemoryError都是內(nèi)存泄漏的信號(hào)。一種查明方法是不間斷地監(jiān)控GC的活動(dòng),確定內(nèi)存使用量是否隨著時(shí)間增加。如果確實(shí)如此,就可能發(fā)生了內(nèi)存泄漏。
?2?處理內(nèi)存泄漏的方法
一旦知道確實(shí)發(fā)生了內(nèi)存泄漏,就需要更專業(yè)的工具來(lái)查明為什么會(huì)發(fā)生泄漏。JVM自己是不會(huì)告訴您的。這些專業(yè)工具從JVM獲得內(nèi)存系統(tǒng)信息的方法基本上有兩種:JVMTI和字節(jié)碼技術(shù)(byte code instrumentation)。Java虛擬機(jī)工具接口(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虛擬機(jī)監(jiān)視程序接口(Java Virtual Machine Profiling Interface,JVMPI)是外部工具與JVM通信并從JVM收集信息的標(biāo)準(zhǔn)化接口。字節(jié)碼技術(shù)是指使用探測(cè)器處理字節(jié)碼以獲得工具所需的信息的技術(shù)。?
Optimizeit是Borland公司的產(chǎn)品,主要用于協(xié)助對(duì)軟件系統(tǒng)進(jìn)行代碼優(yōu)化和故障診斷,其中的Optimizeit Profiler主要用于內(nèi)存泄漏的分析。Profiler的堆視圖就是用來(lái)觀察系統(tǒng)運(yùn)行使用的內(nèi)存大小和各個(gè)類的實(shí)例分配的個(gè)數(shù)的。?
首先,Profiler會(huì)進(jìn)行趨勢(shì)分析,找出是哪個(gè)類的對(duì)象在泄漏。系統(tǒng)運(yùn)行長(zhǎng)時(shí)間后可以得到四個(gè)內(nèi)存快照。對(duì)這四個(gè)內(nèi)存快照進(jìn)行綜合分析,如果每一次快照的內(nèi)存使用都比上一次有增長(zhǎng),可以認(rèn)定系統(tǒng)存在內(nèi)存泄漏,找出在四個(gè)快照中實(shí)例個(gè)數(shù)都保持增長(zhǎng)的類,這些類可以初步被認(rèn)定為存在泄漏。通過(guò)數(shù)據(jù)收集和初步分析,可以得出初步結(jié)論:系統(tǒng)是否存在內(nèi)存泄漏和哪些對(duì)象存在泄漏(被泄漏)。?
接下來(lái),看看有哪些其他的類與泄漏的類的對(duì)象相關(guān)聯(lián)。前面已經(jīng)談到Java中的內(nèi)存泄漏就是無(wú)用的對(duì)象保持,簡(jiǎn)單地說(shuō)就是因?yàn)榫幋a的錯(cuò)誤導(dǎo)致了一條本來(lái)不應(yīng)該存在的引用鏈的存在(從而導(dǎo)致了被引用的對(duì)象無(wú)法釋放),因此內(nèi)存泄漏分析的任務(wù)就是找出這條多余的引用鏈,并找到其形成的原因。查看對(duì)象分配到哪里是很有用的。同時(shí)只知道它們?nèi)绾闻c其他對(duì)象相關(guān)聯(lián)(即哪些對(duì)象引用了它們)是不夠的,關(guān)于它們?cè)诤翁巹?chuàng)建的信息也很有用。?
最后,進(jìn)一步研究單個(gè)對(duì)象,看看它們是如何互相關(guān)聯(lián)的。借助于Profiler工具,應(yīng)用程序中的代碼可以在分配時(shí)進(jìn)行動(dòng)態(tài)添加,以創(chuàng)建堆棧跟蹤。也有可以對(duì)系統(tǒng)中所有對(duì)象分配進(jìn)行動(dòng)態(tài)的堆棧跟蹤。這些堆棧跟蹤可以在工具中進(jìn)行累積和分析。對(duì)每個(gè)被泄漏的實(shí)例對(duì)象,必然存在一條從某個(gè)牽引對(duì)象出發(fā)到達(dá)該對(duì)象的引用鏈。處于堆棧空間的牽引對(duì)象在被從棧中彈出后就失去其牽引的能力,變?yōu)榉菭恳龑?duì)象。因此,在長(zhǎng)時(shí)間的運(yùn)行后,被泄露的對(duì)象基本上都是被作為類的靜態(tài)變量的牽引對(duì)象牽引。?
總而言之, Java雖然有自動(dòng)回收管理內(nèi)存的功能,但內(nèi)存泄漏也是不容忽視,它往往是破壞系統(tǒng)穩(wěn)定性的重要因素。
轉(zhuǎn)載于:https://www.cnblogs.com/catWang/p/4518578.html
總結(jié)
以上是生活随笔為你收集整理的应用jacob组件造成的内存溢出解决方案(java.lang.OutOfMemoryError: Java heap space)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【LA3415 训练指南】保守的老师 【
- 下一篇: Java: 在dos窗口输入密码,不要把