Java中的內(nèi)存泄漏
Java.Lang.OutOfMemoryError: Java Heap Space
Java應(yīng)用程序只允許使用有限的內(nèi)存。此限制在應(yīng)用程序啟動期間指定。為了使事情更復(fù)雜,Java內(nèi)存被分成兩個不同的區(qū)域。這些區(qū)域稱為永久生成區(qū)域(permgene和Permgen):
這些區(qū)域的大小是在Java虛擬機(jī)(JVM)啟動期間設(shè)置的,可以通過指定JVM參數(shù)-Xmx和-XX:MaxPermSize進(jìn)行定制。如果未顯式設(shè)置大小,則將使用特定于平臺的默認(rèn)值。
這個java.lang.OutOfMemoryError:當(dāng)應(yīng)用程序嘗試向堆空間區(qū)域添加更多數(shù)據(jù),但空間不足時,將觸發(fā)Java堆空間錯誤。
請注意,可能有大量可用的物理內(nèi)存,但是java.lang.OutOfMemoryError:每當(dāng)JVM達(dá)到堆大小限制時,就會拋出Java堆空間錯誤。
是什么引起內(nèi)存泄露的?
最常見的原因是java.lang.OutOfMemoryError:Java heap space error –您嘗試將XXL應(yīng)用程序放入S大小的Java堆空間中。只是Java應(yīng)用程序需要更多的空間來操作。此OutOfMemoryError消息的其他原因更為復(fù)雜,是由編程錯誤引起的:
- 使用量/數(shù)據(jù)量激增。該應(yīng)用程序設(shè)計(jì)為處理一定數(shù)量的用戶或一定數(shù)量的數(shù)據(jù)。當(dāng)用戶數(shù)量或數(shù)據(jù)量突然達(dá)到峰值并超過預(yù)期閾值時,在峰值停止運(yùn)行并觸發(fā)java.lang.OutOfMemoryError:Java堆空間錯誤。
- 內(nèi)存泄漏。特定類型的編程錯誤將導(dǎo)致應(yīng)用程序不斷消耗更多內(nèi)存。每次使用應(yīng)用程序的泄漏功能時,它都會將一些對象留在Java堆空間中。隨著時間的推移,泄漏的對象會消耗所有可用的Java堆空間,并觸發(fā)已經(jīng)熟悉的java.lang.OutOfMemoryError:Java堆空間錯誤。
內(nèi)存泄漏代碼示例
第一個例子非常簡單–下面的Java代碼嘗試分配一個2M整數(shù)數(shù)組。當(dāng)您編譯它并使用12MB的Java堆空間(Java-Xmx12m-OOM)啟動時,它將失敗java.lang.OutOfMemoryError:Java堆空間消息。有了13MB的Java堆空間,程序運(yùn)行得很好。
class OOM { static final int SIZE=2*1024*1024; public static void main(String[] a) { int[] i = new int[SIZE]; } }
第二個也是更現(xiàn)實(shí)的例子是內(nèi)存泄漏在Java中,當(dāng)開發(fā)人員創(chuàng)建和使用新的對象(如new Integer(5))時,他們不必自己分配內(nèi)存—這是由Java虛擬機(jī)(JVM)負(fù)責(zé)的。在應(yīng)用程序的生命周期中,JVM會定期檢查內(nèi)存中哪些對象仍在使用,哪些對象沒有使用。未使用的對象可以丟棄,內(nèi)存可以回收并再次使用。這個過程稱為垃圾回收。JVM中負(fù)責(zé)收集的相應(yīng)模塊稱為垃圾收集器(GC)。
Java的自動內(nèi)存管理依賴于GC定期查找未使用的對象并將其刪除。簡單地說,java 內(nèi)存泄露是指應(yīng)用程序不再使用某些對象,但垃圾回收無法識別它的情況。因此,這些未使用的對象將無限期地保留在Java堆空間中。這起連環(huán)碰撞最終會觸發(fā)java.lang.OutOfMemoryError:Java堆空間錯誤。
構(gòu)建一個滿足內(nèi)存泄漏定義的Java程序相當(dāng)容易:
class KeylessEntry { static class Key { Integer id; Key(Integer id) { this.id = id; } @Override public int hashCode() { return id.hashCode(); } } public static void main(String[] args) { Map m = new HashMap(); while (true) for (int i = 0; i < 10000; i++) if (!m.containsKey(new Key(i))) m.put(new Key(i), "Number:" + i); } }
當(dāng)您執(zhí)行上面的代碼時,您可能希望它永遠(yuǎn)運(yùn)行而不會出現(xiàn)任何問題,假設(shè)天真的緩存解決方案只將底層映射擴(kuò)展到10000個元素,除此之外,所有的鍵都已經(jīng)存在于HashMap中。但是,實(shí)際上,由于Key類在hashCode()旁邊沒有適當(dāng)?shù)膃quals()實(shí)現(xiàn),所以元素將繼續(xù)被添加。
結(jié)果,隨著時間的推移,隨著泄漏代碼的不斷使用,“緩存”結(jié)果最終會消耗大量Java堆空間。當(dāng)泄漏的內(nèi)存填滿堆區(qū)域中的所有可用內(nèi)存,而垃圾回收無法清理它時java.lang.OutOfMemoryError:引發(fā)Java堆空間。
解決方案很簡單–添加與下面類似的equals()方法的實(shí)現(xiàn),您就可以開始了。但在你找到病因之前,你肯定會失去一些珍貴的腦細(xì)胞。
@Override public boolean equals(Object o) { boolean response = false; if (o instanceof Key) { response = (((Key)o).id).equals(this.id); } return response; }
內(nèi)存溢出怎么解決?
在某些情況下,分配給JVM的堆的數(shù)量不足以滿足在JVM上運(yùn)行的應(yīng)用程序的需要。在這種情況下,您應(yīng)該只分配更多的堆—請參閱本章末尾的部分了解如何實(shí)現(xiàn)這一點(diǎn)。
然而,在許多情況下,提供更多的Java堆空間并不能解決問題。例如,如果應(yīng)用程序包含內(nèi)存泄漏,則添加更多堆只會推遲java.lang.OutOfMemoryError:Java堆空間錯誤。此外,增加Java堆空間量也會增加GC暫停的長度,從而影響應(yīng)用程序的吞吐量或延遲。
如果您希望解決Java堆空間的底層問題,而不是掩蓋癥狀,那么您需要找出代碼的哪一部分負(fù)責(zé)分配最多的內(nèi)存。換句話說,你需要回答以下問題:
- 哪些對象占據(jù)堆的大部分
- 在源代碼中分配這些對象
在這一點(diǎn)上,一定要在你的日歷中清除幾天(或者-在項(xiàng)目符號列表下面自動查看)。下面是一個粗略的流程大綱,可以幫助您回答上述問題:
- 獲得安全許可,以便從JVM執(zhí)行堆轉(zhuǎn)儲?!癲ump轉(zhuǎn)儲”基本上是堆內(nèi)容的快照,您可以對其進(jìn)行分析。因此,這些快照可能包含機(jī)密信息,如密碼、信用卡號碼等,因此出于安全原因,獲取此類轉(zhuǎn)儲甚至可能不可能。
- 在適當(dāng)?shù)臅r候把垃圾處理掉。準(zhǔn)備好獲取一些轉(zhuǎn)儲,因?yàn)楫?dāng)在錯誤的時間執(zhí)行時,堆轉(zhuǎn)儲包含大量的噪聲,實(shí)際上可能是無用的。另一方面,每個堆轉(zhuǎn)儲都會完全“freezes凍結(jié)”JVM,所以不要占用太多,否則最終用戶將面臨性能問題。
- 找一臺能裝垃圾的機(jī)器。當(dāng)您的JVM使用例如8GB的堆時,您需要一臺大于8GB的機(jī)器來分析堆內(nèi)容。啟動轉(zhuǎn)儲分析軟件(我們推薦Eclipse MAT,但也有同樣好的替代品)。
- 檢測堆的最大使用者的GC根路徑。我們在這里的另一篇文章中討論了這一活動。這對初學(xué)者來說尤其困難,但實(shí)踐將使你了解結(jié)構(gòu)和導(dǎo)航機(jī)制。
- 接下來,您需要弄清楚源代碼中潛在危險(xiǎn)的大量對象被分配到哪里。如果您對應(yīng)用程序的源代碼有很好的了解,那么您將能夠在幾次搜索中做到這一點(diǎn)。
或者,我們建議使用plumber,這是唯一一個具有自動根本原因檢測功能的Java監(jiān)控解決方案。在其他性能問題中,它包羅萬象java.lang.OutOfMemoryErrors并自動為您提供有關(guān)最需要內(nèi)存的數(shù)據(jù)結(jié)構(gòu)的信息。
Plumber負(fù)責(zé)在后臺收集必要的數(shù)據(jù)——這包括關(guān)于堆使用情況的相關(guān)數(shù)據(jù)(只有對象布局圖,沒有實(shí)際數(shù)據(jù)),還有一些甚至在堆轉(zhuǎn)儲中都找不到的數(shù)據(jù)。它還為您執(zhí)行必要的數(shù)據(jù)處理—在運(yùn)行中,只要JVM遇到j(luò)ava.lang.OutOfMemoryError. 這里有一個例子java.lang.OutOfMemoryError管道工事故警報(bào):
無需任何其他工具或分析,您可以看到:
- 哪些對象消耗的內(nèi)存最多
- 在哪里分配這些對象(它們中的大多數(shù)在MetricManagerImpl類中分配,第304行)
- 當(dāng)前引用這些對象的是什么(到GC根的完整引用鏈)
有了這些信息,您就可以放大潛在的根本原因,并確保將數(shù)據(jù)結(jié)構(gòu)縮減到適合您的內(nèi)存池的級別。
然而,當(dāng)您從內(nèi)存分析或閱讀plumber報(bào)告得出的結(jié)論是內(nèi)存使用是合法的,并且源代碼中沒有什么可更改的,那么您需要允許JVM有更多的Java堆空間來正常運(yùn)行。在這種情況下,更改JVM啟動配置并添加(或增加值,如果存在):
-Xmx1024m
上述配置將為應(yīng)用程序提供1024MB的Java堆空間??梢允褂胓或g表示GB,m或m表示MB,k或k表示KB。例如,以下所有內(nèi)容都相當(dāng)于最大Java堆空間為1GB:
java -Xmx1073741824 com.mycompany.MyClass java -Xmx1048576k com.mycompany.MyClass java -Xmx1024m com.mycompany.MyClass java -Xmx1g com.mycompany.MyClass
以上就是Java中的內(nèi)存泄漏的詳細(xì)內(nèi)容,更多關(guān)于Java 內(nèi)存泄漏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java中servlet實(shí)現(xiàn)登錄驗(yàn)證的方法
做web開發(fā),登錄驗(yàn)證是免不了的,今天學(xué)習(xí)了servlet的登錄驗(yàn)證,當(dāng)然是很簡單的,沒有使用session,request等作用域?qū)ο?,所以還是可以直接通過地址訪問網(wǎng)頁的。2013-05-05Idea如何關(guān)閉或開啟引用提示Usages和Annotations
這篇文章主要介紹了Idea如何關(guān)閉或開啟引用提示Usages和Annotations問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01SpringMVC框架和SpringBoot項(xiàng)目中控制器的響應(yīng)結(jié)果深入分析
這篇文章主要介紹了SpringMVC框架和SpringBoot項(xiàng)目中控制器的響應(yīng)結(jié)果,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12Spring框架JavaMailSender發(fā)送郵件工具類詳解
這篇文章主要為大家詳細(xì)介紹了Spring框架JavaMailSender發(fā)送郵件工具類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04SpringBoot綁定配置文件中變量的四種方式總結(jié)
當(dāng)在Spring Boot中需要綁定配置文件中的變量時,可以使用以下注解:@PropertySourc,@Value,@Environment,@ConfigurationProperties,具體實(shí)現(xiàn)代碼示例文中講解的非常詳細(xì),需要的朋友可以參考下2023-11-11Spring整合Quartz分布式調(diào)度的示例代碼
本篇文章主要介紹了Spring整合Quartz分布式調(diào)度的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04淺談resultMap的用法及關(guān)聯(lián)結(jié)果集映射
這篇文章主要介紹了resultMap的用法及關(guān)聯(lián)結(jié)果集映射操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06