如何排查Java內(nèi)存泄漏?看完我給跪了!
往期熱門文章: 1、當(dāng) Docker 遇上 IDEA ,生產(chǎn)力徹底炸裂了 2、如何把Spring Boot的Jar包做成exe?超詳細(xì)教程來(lái)了! 3、徹底搞懂 Nginx 的五大應(yīng)用場(chǎng)景 4、SpringBoot+Dubbo是如何搞定微服務(wù),成功應(yīng)對(duì)高并發(fā)的? 5、為什么有些大公司技術(shù)弱爆了? 作者:Jose Ferreirade Souza Filho 譯者:Emma 來(lái)源:www.toptal.com/java/hunting-memory-leaks-in-java
沒(méi)有經(jīng)驗(yàn)的程序員經(jīng)常認(rèn)為Java的自動(dòng)垃圾回收完全使他們免于擔(dān)心內(nèi)存管理。這是一個(gè)常見(jiàn)的誤解:雖然垃圾收集器做得很好,但即使是最好的程序員也完全有可能成為嚴(yán)重破壞內(nèi)存泄漏的犧牲品。讓我解釋一下。
當(dāng)不必要地維護(hù)不再需要的對(duì)象引用時(shí),會(huì)發(fā)生內(nèi)存泄漏。這些泄漏很糟糕。首先,當(dāng)程序消耗越來(lái)越多的資源時(shí),它們會(huì)對(duì)計(jì)算機(jī)施加不必要的壓力。更糟糕的是,檢測(cè)這些泄漏可能很困難:靜態(tài)分析通常很難精確識(shí)別這些冗余引用,現(xiàn)有的泄漏檢測(cè)工具會(huì)跟蹤和報(bào)告有關(guān)單個(gè)對(duì)象的細(xì)粒度信息,產(chǎn)生難以解釋且缺乏精確度的結(jié)果。
換句話說(shuō),泄漏要么太難以識(shí)別,要么使用太過(guò)具體而無(wú)用術(shù)語(yǔ)來(lái)識(shí)別。
實(shí)際上有四類內(nèi)存問(wèn)題具有相似和重疊的特征,但原因和解決方案各不相同:
Performance(性能):通常與過(guò)多的對(duì)象創(chuàng)建和刪除,垃圾收集的長(zhǎng)時(shí)間延遲,過(guò)多的操作系統(tǒng)頁(yè)面交換等相關(guān)聯(lián)。
Resource constraints(資源約束):當(dāng)可用內(nèi)存很少或內(nèi)存過(guò)于分散而無(wú)法分配大對(duì)象時(shí) - 這可能是本機(jī)的,或者更常見(jiàn)的是與Java堆相關(guān)。
Java heap leaks(java堆泄漏):經(jīng)典的內(nèi)存泄漏,Java對(duì)象在不釋放的情況下不斷創(chuàng)建。這通常是由潛在對(duì)象引用引起的。
Native memory leaks(本機(jī)內(nèi)存泄漏):與Java堆之外的任何不斷增長(zhǎng)的內(nèi)存利用率相關(guān)聯(lián),例如由JNI代碼,驅(qū)動(dòng)程序甚至JVM分配。
在這個(gè)內(nèi)存管理教程中,我將專注于Java堆漏洞,并概述一種基于Java VisualVM報(bào)告檢測(cè)此類泄漏的方法,并利用可視化界面在運(yùn)行時(shí)分析基于Java技術(shù)的應(yīng)用程序。
但在您可以預(yù)防和發(fā)現(xiàn)內(nèi)存泄漏之前,您應(yīng)該了解它們的發(fā)生方式和原因。(注意:如果你能很好地處理錯(cuò)綜復(fù)雜的內(nèi)存泄漏,你可以跳過(guò)。)
1. 內(nèi)存泄漏:基礎(chǔ)
對(duì)于初學(xué)者來(lái)說(shuō),將內(nèi)存泄漏視為一種疾病,將Java的OutOfMemoryError(簡(jiǎn)稱OOM)視為一種癥狀。但與任何疾病一樣,并非所有OOM都意味著內(nèi)存泄漏:由于生成大量局部變量或其他此類事件,OOM可能會(huì)發(fā)生。另一方面,并非所有內(nèi)存泄漏都必然表現(xiàn)為OOM,特別是在桌面應(yīng)用程序或客戶端應(yīng)用程序(沒(méi)有重新啟動(dòng)時(shí)運(yùn)行很長(zhǎng)時(shí)間)的情況下。
將內(nèi)存泄漏視為疾病,將OutOfMemoryError視為癥狀。但并非所有OutOfMemoryErrors都意味著內(nèi)存泄漏,并非所有內(nèi)存泄漏都表現(xiàn)為OutOfMemoryErrors。
為什么這些泄漏如此糟糕?除此之外,程序執(zhí)行期間泄漏的內(nèi)存塊通常會(huì)降低系統(tǒng)性能,因?yàn)榉峙涞词褂玫膬?nèi)存塊必須在系統(tǒng)耗盡空閑物理內(nèi)存時(shí)進(jìn)行換出。最終,程序甚至可能耗盡其可用的虛擬地址空間,從而導(dǎo)致OOM。
2. 解密OutOfMemoryError
如上所述,OOM是內(nèi)存泄漏的常見(jiàn)指示。實(shí)質(zhì)上,當(dāng)沒(méi)有足夠的空間來(lái)分配新對(duì)象時(shí),會(huì)拋出錯(cuò)誤。當(dāng)垃圾收集器找不到必要的空間,并且堆不能進(jìn)一步擴(kuò)展,會(huì)多次嘗試。因此,會(huì)出現(xiàn)錯(cuò)誤以及堆棧跟蹤。
診斷OOM的第一步是確定錯(cuò)誤的實(shí)際含義。這聽(tīng)起來(lái)很清楚,但答案并不總是那么清晰。例如:OOM是否是因?yàn)镴ava堆已滿而出現(xiàn),還是因?yàn)楸緳C(jī)堆已滿?為了幫助您回答這個(gè)問(wèn)題,讓我們分析一些可能的錯(cuò)誤消息:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
java.lang.OutOfMemoryError: request bytes for . Out of swap space?
java.lang.OutOfMemoryError: (Native method)
2.1.“Java heap space”
此錯(cuò)誤消息不一定意味著內(nèi)存泄漏。實(shí)際上,問(wèn)題可能與配置問(wèn)題一樣簡(jiǎn)單。
例如,我負(fù)責(zé)分析一直產(chǎn)生這種類型的OutOfMemoryError的應(yīng)用程序。經(jīng)過(guò)一番調(diào)查后,我發(fā)現(xiàn)罪魁禍?zhǔn)资顷嚵袑?shí)例化,因?yàn)樾枰嗟膬?nèi)存;在這種情況下,并不是應(yīng)用程序的錯(cuò),而是應(yīng)用程序服務(wù)器依賴于默認(rèn)的堆太小了。我通過(guò)調(diào)整JVM的內(nèi)存參數(shù)解決了這個(gè)問(wèn)題。
在其他情況下,特別是對(duì)于長(zhǎng)期存在的應(yīng)用程序,該消息可能表明我們無(wú)意中持有對(duì)象的引用,從而阻止垃圾收集器清理它們。這時(shí)Java語(yǔ)言等同于內(nèi)存泄漏。(注意:應(yīng)用程序調(diào)用的API也可能無(wú)意中持有對(duì)象引用。)
這些“Java堆空間”O(jiān)OM的另一個(gè)潛在來(lái)源是使用finalizers。如果類具有finalize方法,則在垃圾收集時(shí)該類型的對(duì)象不會(huì)被回收。而是在垃圾收集之后,稍后對(duì)象將排隊(duì)等待最終確定。在Sun實(shí)現(xiàn)中,finalizers由守護(hù)線程執(zhí)行。如果finalizers線程無(wú)法跟上finalization隊(duì)列,那么Java堆可能會(huì)填滿并且可能拋出OOM。
2.2. “PermGen space”
此錯(cuò)誤消息表明永久代已滿。永久代是存儲(chǔ)類和方法對(duì)象的堆的區(qū)域。如果應(yīng)用程序加載了大量類,則可能需要使用-XX:MaxPermSize選項(xiàng)增加永久代的大小。
Interned java.lang.String對(duì)象也存儲(chǔ)在永久代中。java.lang.String類維護(hù)一個(gè)字符串池。調(diào)用實(shí)習(xí)方法時(shí),該方法檢查池以查看是否存在等效字符串。如果是這樣,它由實(shí)習(xí)方法返回;如果沒(méi)有,則將字符串添加到池中。更準(zhǔn)確地說(shuō),java.lang.String.intern方法返回一個(gè)字符串的規(guī)范表示;結(jié)果是對(duì)該字符串顯示為文字時(shí)將返回的同一個(gè)類實(shí)例的引用。如果應(yīng)用程序?qū)嵗罅孔址瑒t可能需要增加永久代的大小。
注意:您可以使用jmap -permgen命令打印與永久生成相關(guān)的統(tǒng)計(jì)信息,包括有關(guān)內(nèi)部化String實(shí)例的信息。
2.3.“Requested array size exceeds VM limit”
此錯(cuò)誤表示應(yīng)用程序(或該應(yīng)用程序使用的API)嘗試分配大于堆大小的數(shù)組。例如,如果應(yīng)用程序嘗試分配512MB的數(shù)組但最大堆大小為256MB,則將拋出此錯(cuò)誤消息的OOM。在大多數(shù)情況下,問(wèn)題是配置問(wèn)題或應(yīng)用程序嘗試分配海量數(shù)組時(shí)導(dǎo)致的錯(cuò)誤。
2.4. “Request bytes for . Out of swap space?”
此消息似乎是一個(gè)OOM。但是,當(dāng)本機(jī)堆的分配失敗并且本機(jī)堆可能將被耗盡時(shí),HotSpot VM會(huì)拋出此異常。消息中包括失敗請(qǐng)求的大小(以字節(jié)為單位)以及內(nèi)存請(qǐng)求的原因。在大多數(shù)情況下,是報(bào)告分配失敗的源模塊的名稱。
如果拋出此類型的OOM,則可能需要在操作系統(tǒng)上使用故障排除實(shí)用程序來(lái)進(jìn)一步診斷問(wèn)題。在某些情況下,問(wèn)題甚至可能與應(yīng)用程序無(wú)關(guān)。例如,您可能會(huì)在以下情況下看到此錯(cuò)誤:
操作系統(tǒng)配置的交換空間不足。
系統(tǒng)上的另一個(gè)進(jìn)程是消耗所有可用的內(nèi)存資源。
由于本機(jī)泄漏,應(yīng)用程序也可能失敗(例如,如果某些應(yīng)用程序或庫(kù)代碼不斷分配內(nèi)存但無(wú)法將其釋放到操作系統(tǒng))。
2.5. Native method
如果您看到此錯(cuò)誤消息并且堆棧跟蹤的頂部框架是本機(jī)方法,則該本機(jī)方法遇到分配失敗。此消息與上一個(gè)消息之間的區(qū)別在于,在JNI或本機(jī)方法中檢測(cè)到Java內(nèi)存分配失敗,而不是在Java VM代碼中檢測(cè)到。
如果拋出此類型的OOM,您可能需要在操作系統(tǒng)上使用實(shí)用程序來(lái)進(jìn)一步診斷問(wèn)題。
2.6. Application Crash Without OOM
有時(shí),應(yīng)用程序可能會(huì)在從本機(jī)堆分配失敗后很快崩潰。如果您運(yùn)行的本機(jī)代碼不檢查內(nèi)存分配函數(shù)返回的錯(cuò)誤,則會(huì)發(fā)生這種情況。
例如,如果沒(méi)有可用內(nèi)存,malloc系統(tǒng)調(diào)用將返回NULL。如果未檢查malloc的返回,則應(yīng)用程序在嘗試訪問(wèn)無(wú)效的內(nèi)存位置時(shí)可能會(huì)崩潰。根據(jù)具體情況,可能很難定位此類問(wèn)題。
在某些情況下,致命錯(cuò)誤日志或崩潰轉(zhuǎn)儲(chǔ)的信息就足以診斷問(wèn)題。如果確定崩潰的原因是某些內(nèi)存分配中缺少錯(cuò)誤處理,那么您必須找到所述分配失敗的原因。與任何其他本機(jī)堆問(wèn)題一樣,系統(tǒng)可能配置了但交換空間不足,另一個(gè)進(jìn)程可能正在消耗所有可用內(nèi)存資源等。
3. 泄漏診斷
在大多數(shù)情況下,診斷內(nèi)存泄漏需要非常詳細(xì)地了解相關(guān)應(yīng)用程序。警告:該過(guò)程可能很長(zhǎng)并且是迭代的。
我們尋找內(nèi)存泄漏的策略將相對(duì)簡(jiǎn)單:
識(shí)別癥狀
啟用詳細(xì)垃圾回收
啟用分析
分析蹤跡
3.1. 識(shí)別癥狀
正如所討論的,在許多情況下,Java進(jìn)程最終會(huì)拋出一個(gè)OOM運(yùn)行時(shí)異常,這是一個(gè)明確的指示,表明您的內(nèi)存資源已經(jīng)耗盡。在這種情況下,您需要區(qū)分正常的內(nèi)存耗盡和泄漏。分析OOM的消息并嘗試根據(jù)上面提供的討論找到罪魁禍?zhǔn)住?/span>
通常,如果Java應(yīng)用程序請(qǐng)求的存儲(chǔ)空間超過(guò)運(yùn)行時(shí)堆提供的存儲(chǔ)空間,則可能是由于設(shè)計(jì)不佳導(dǎo)致的。例如,如果應(yīng)用程序創(chuàng)建映像的多個(gè)副本或?qū)⑽募虞d到數(shù)組中,則當(dāng)映像或文件非常大時(shí),它將耗盡存儲(chǔ)空間。這是正常的資源耗盡。該應(yīng)用程序按設(shè)計(jì)工作(雖然這種設(shè)計(jì)顯然是愚蠢的)。
但是,如果應(yīng)用程序在處理相同類型的數(shù)據(jù)時(shí)穩(wěn)定地增加其內(nèi)存利用率,則可能會(huì)發(fā)生內(nèi)存泄漏。
3.2. 啟用詳細(xì)垃圾收集
斷言確實(shí)存在內(nèi)存泄漏的最快方法之一是啟用詳細(xì)垃圾回收。通常可以通過(guò)檢查verbosegc輸出中的模式來(lái)識(shí)別內(nèi)存約束問(wèn)題。
具體來(lái)說(shuō),-verbosegc參數(shù)允許您在每次垃圾收集(GC)過(guò)程開始時(shí)生成跟蹤。也就是說(shuō),當(dāng)內(nèi)存被垃圾收集時(shí),摘要報(bào)告會(huì)打印到標(biāo)準(zhǔn)錯(cuò)誤,讓您了解內(nèi)存的管理方式。
這是使用-verbosegc選項(xiàng)生成的一些典型輸出:

image
此GC跟蹤文件中的每個(gè)塊(或節(jié))按遞增順序編號(hào)。要理解這種跟蹤,您應(yīng)該查看連續(xù)的分配失敗節(jié),并查找隨著時(shí)間的推移而減少的釋放內(nèi)存(字節(jié)和百分比),同時(shí)總內(nèi)存(此處,19725304)正在增加。這些是內(nèi)存耗盡的典型跡象。
3.3. 啟用分析
不同的JVM提供了生成跟蹤文件以反映堆活動(dòng)的不同方法,這些方法通常包括有關(guān)對(duì)象類型和大小的詳細(xì)信息。這稱為分析堆。
3.4. 分析路徑
本文重點(diǎn)介紹Java VisualVM生成的跟蹤。跟蹤可以有不同的格式,因?yàn)樗鼈兛梢杂刹煌腏ava內(nèi)存泄漏檢測(cè)工具生成,但它們背后的想法總是相同的:在堆中找到不應(yīng)該存在的對(duì)象塊,并確定這些對(duì)象是否累積而不是釋放。特別感興趣的是每次在Java應(yīng)用程序中觸發(fā)某個(gè)事件時(shí)已知的臨時(shí)對(duì)象。應(yīng)該僅存少量,但存在許多對(duì)象實(shí)例,通常表示應(yīng)用程序出現(xiàn)錯(cuò)誤。
最后,解決內(nèi)存泄漏需要您徹底檢查代碼。了解對(duì)象泄漏的類型可能對(duì)此非常有用,并且可以大大加快調(diào)試速度。
4. 垃圾收集如何在JVM中運(yùn)行?
在我們開始分析具有內(nèi)存泄漏問(wèn)題的應(yīng)用程序之前,讓我們首先看看垃圾收集在JVM中的工作原理。
JVM使用一種稱為跟蹤收集器的垃圾收集器,它基本上通過(guò)暫停它周圍的世界來(lái)操作,標(biāo)記所有根對(duì)象(由運(yùn)行線程直接引用的對(duì)象),并遵循它們的引用,標(biāo)記它沿途看到的每個(gè)對(duì)象。
Java基于分代假設(shè)-實(shí)現(xiàn)了一種稱為分代垃圾收集器的東西,該假設(shè)表明創(chuàng)建的大多數(shù)對(duì)象被快速丟棄,而未快速收集的對(duì)象可能會(huì)存在一段時(shí)間。
基于此假設(shè),[Java將對(duì)象分為多代](http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html#1.1. Generations|outline)。這是一個(gè)視覺(jué)解釋:

image
Young Generation -這是對(duì)象的開始。它有兩個(gè)子代
Eden Space -對(duì)象從這里開始。大多數(shù)物體都是在Eden Space中創(chuàng)造和銷毀的。在這里,GC執(zhí)行Minor GCs,這是優(yōu)化的垃圾收集。執(zhí)行Minor GC時(shí),對(duì)仍然需要的對(duì)象的任何引用都將遷移到其中一個(gè)survivors空間(S0或S1)。
Survivor Space (S0 and S1)-幸存Eden Space的對(duì)象最終來(lái)到這里。其中有兩個(gè),在任何給定時(shí)間只有一個(gè)正在使用(除非我們有嚴(yán)重的內(nèi)存泄漏)。一個(gè)被指定為空,另一個(gè)被指定為活動(dòng),與每個(gè)GC循環(huán)交替。
Tenured Generation -也被稱為老年代(圖2中的舊空間),這個(gè)空間容納存活較長(zhǎng)的對(duì)象,使用壽命更長(zhǎng)(如果它們活得足夠長(zhǎng),則從Survivor空間移過(guò)來(lái))。填充此空間時(shí),GC會(huì)執(zhí)行完整GC,這會(huì)在性能方面降低成本。如果此空間無(wú)限制地增長(zhǎng),則JVM將拋出OutOfMemoryError - Java堆空間。
Permanent Generation -作為與終身代密切相關(guān)的第三代,永久代是特殊的,因?yàn)樗4嫣摂M機(jī)所需的數(shù)據(jù),以描述在Java語(yǔ)言級(jí)別上沒(méi)有等價(jià)的對(duì)象。例如,描述類和方法的對(duì)象存儲(chǔ)在永久代中。
Java足夠聰明,可以為每一代應(yīng)用不同的垃圾收集方法。使用名為Parallel New Collector的跟蹤復(fù)制收集器處理年輕代。這個(gè)收集器阻止了這個(gè)世界,但由于年輕一代通常很小,所以暫停很短暫。
有關(guān)JVM代及其工作原理的更多信息,請(qǐng)查閱Memory Management in the Java HotSpot? Virtual Machine 。
5. 檢測(cè)內(nèi)存泄漏
要查找內(nèi)存泄漏并消除它們,您需要合適的內(nèi)存泄漏工具。是時(shí)候使用Java VisualVM檢測(cè)并刪除此類泄漏。
5.1. 使用Java VisualVM遠(yuǎn)程分析堆
VisualVM是一種工具,它提供了一個(gè)可視化界面,用于查看有關(guān)基于Java技術(shù)的應(yīng)用程序運(yùn)行時(shí)的詳細(xì)信息。
使用VisualVM,您可以查看與本地應(yīng)用程序和遠(yuǎn)程主機(jī)上運(yùn)行的應(yīng)用程序相關(guān)的數(shù)據(jù)。您還可以捕獲有關(guān)JVM軟件實(shí)例的數(shù)據(jù),并將數(shù)據(jù)保存到本地系統(tǒng)。
為了從Java VisualVM的所有功能中受益,您應(yīng)該運(yùn)行Java平臺(tái)標(biāo)準(zhǔn)版(Java SE)版本6或更高版本。
Related: Why You Need to Upgrade to Java 8 Already
5.2. 為JVM啟用遠(yuǎn)程連接
在生產(chǎn)環(huán)境中,通常很難訪問(wèn)運(yùn)行代碼的實(shí)際機(jī)器。幸運(yùn)的是,我們可以遠(yuǎn)程分析我們的Java應(yīng)用程序。
首先,我們需要在目標(biāo)機(jī)器上授予自己JVM訪問(wèn)權(quán)限。為此,請(qǐng)使用以下內(nèi)容創(chuàng)建名為jstatd.all.policy的文件:
grant codebase "file:${java.home}/../lib/tools.jar"{
permission java.security.AllPermission;
};
創(chuàng)建文件后,我們需要使用jstatd - Virtual Machine jstat Daemon工具啟用與目標(biāo)VM的遠(yuǎn)程連接,如下所示:
jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>
例如:
jstatd -p 1234-J-Djava.security.policy=D:\jstatd.all.policy
通過(guò)在目標(biāo)VM中啟動(dòng)jstatd,我們能夠連接到目標(biāo)計(jì)算機(jī)并遠(yuǎn)程分析應(yīng)用程序的內(nèi)存泄漏問(wèn)題。
5.3. 連接到遠(yuǎn)程主機(jī)
在客戶端計(jì)算機(jī)中,打開提示并鍵入jvisualvm以打開VisualVM工具。
接下來(lái),我們必須在VisualVM中添加遠(yuǎn)程主機(jī)。當(dāng)目標(biāo)JVM啟用以允許來(lái)自具有J2SE 6或更高版本的另一臺(tái)計(jì)算機(jī)的遠(yuǎn)程連接時(shí),我們啟動(dòng)Java VisualVM工具并連接到遠(yuǎn)程主機(jī)。如果與遠(yuǎn)程主機(jī)的連接成功,我們將看到在目標(biāo)JVM中運(yùn)行的Java應(yīng)用程序,如下所示:
要在應(yīng)用程序上運(yùn)行內(nèi)存分析器,我們只需在側(cè)面板中雙擊其名稱即可。
現(xiàn)在我們已經(jīng)設(shè)置了內(nèi)存分析器,讓我們研究一個(gè)內(nèi)存泄漏問(wèn)題的應(yīng)用程序,我們稱之為MemLeak。
6. MemLeak
當(dāng)然,有很多方法可以在Java中創(chuàng)建內(nèi)存泄漏。為簡(jiǎn)單起見(jiàn),我們將一個(gè)類定義為HashMap中的鍵,但我們不會(huì)定義equals()和hashcode()方法。
HashMap是Map接口的哈希表實(shí)現(xiàn),因此它定義了鍵和值的基本概念:每個(gè)值都與唯一鍵相關(guān),因此如果給定鍵值對(duì)的鍵已經(jīng)存在于HashMap,它的當(dāng)前值被替換。
我們的密鑰類必須提供equals()和hashcode()方法的正確實(shí)現(xiàn)。沒(méi)有它們,就無(wú)法保證會(huì)生成一個(gè)好的密鑰。
通過(guò)不定義equals()和hashcode()方法,我們一遍又一遍地向HashMap添加相同的鍵,而不是按原樣替換鍵,HashMap不斷增長(zhǎng),無(wú)法識(shí)別這些相同的鍵并拋出OutOfMemoryError 。
MemLeak類:
package com.post.memory.leak;
import java.util.Map;
publicclassMemLeak{
publicfinalString key;
publicMemLeak(String key) {
this.key =key;
}
publicstaticvoid main(String args[]) {
try{
Map map = System.getProperties();
for(;;) {
map.put(newMemLeak("key"), "value");
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
注意:內(nèi)存泄漏不是由于第14行的無(wú)限循環(huán):無(wú)限循環(huán)可能導(dǎo)致資源耗盡,但不會(huì)導(dǎo)致內(nèi)存泄漏。如果我們已經(jīng)正確實(shí)現(xiàn)了equals()和hashcode()方法,那么即使使用無(wú)限循環(huán),代碼也能正常運(yùn)行,因?yàn)槲覀冊(cè)贖ashMap中只有一個(gè)元素。
(對(duì)于那些感興趣的人,這里有一些(故意)產(chǎn)生泄漏的替代方法。)
7. 使用Java VisualVM
使用Java VisualVM,我們可以對(duì)Java Heap進(jìn)行內(nèi)存監(jiān)視,并確定其行為是否存在內(nèi)存泄漏。
這是剛剛初始化后MemLeak的Java堆分析器的圖形表示(回想一下我們對(duì)各代的討論):

image
僅僅30秒之后,老年代幾乎已滿,表明即使使用Full GC,老年代也在不斷增長(zhǎng),這是內(nèi)存泄漏的明顯跡象。
檢測(cè)此泄漏原因的一種方法如下圖所示(單擊放大),使用帶有heapdump的Java VisualVM生成。在這里,我們看到50%的Hashtable $ Entry對(duì)象在堆中,而第二行指向MemLeak類。因此,內(nèi)存泄漏是由MemLeak類中使用的哈希表引起的。

image
最后,在OutOfMemoryError之后觀察Java Heap,其中Young和Old代完全填滿。

image
8. 結(jié)束語(yǔ)
內(nèi)存泄漏是最難解決的Java應(yīng)用程序問(wèn)題之一,因?yàn)榘Y狀多種多樣且難以重現(xiàn)。在這里,我們概述了一種逐步發(fā)現(xiàn)內(nèi)存泄漏并確定其來(lái)源的方法。但最重要的是,仔細(xì)閱讀您的錯(cuò)誤消息并注意堆棧跟蹤 - 并非所有泄漏都像它們出現(xiàn)的那樣簡(jiǎn)單。
9. 附錄
與Java VisualVM一起,還有其他幾種可以執(zhí)行內(nèi)存泄漏檢測(cè)的工具。許多泄漏檢測(cè)器通過(guò)攔截對(duì)存儲(chǔ)器管理例程的調(diào)用在庫(kù)級(jí)別操作。例如,HPROF是一個(gè)與Java 2平臺(tái)標(biāo)準(zhǔn)版(J2SE)捆綁在一起的簡(jiǎn)單命令行工具,用于堆和CPU分析。可以直接分析HPROF的輸出,或?qū)⑵溆米鱆HAT等其他工具的輸入。當(dāng)我們使用Java 2 Enterprise Edition(J2EE)應(yīng)用程序時(shí),有許多堆轉(zhuǎn)儲(chǔ)分析器解決方案更友好,例如IBM Heapdumps for Websphere應(yīng)用程序服務(wù)器。
最近熱文閱讀:
1、當(dāng) Docker 遇上 IDEA ,生產(chǎn)力徹底炸裂了 2、如何把Spring Boot的Jar包做成exe?超詳細(xì)教程來(lái)了! 3、徹底搞懂 Nginx 的五大應(yīng)用場(chǎng)景 4、推薦60個(gè)相見(jiàn)恨晚的神器工具 5、為什么有些大公司技術(shù)弱爆了? 6、這 40 道 Redis 面試題讓你不再慌(附答案) 7、優(yōu)秀的代碼都是如何分層的? 8、IDEA 中的熱部署神器! 9、SpringBean默認(rèn)是單例的,高并發(fā)情況下,如何保證并發(fā)安全? 10、知乎高贊:拼多多和國(guó)家電網(wǎng),選哪個(gè)? 關(guān)注公眾號(hào),你想要的Java都在這里
