更新時間:2019-09-04 09:55:48 來源:動力節點 瀏覽2356次
如何在Java應用程序中發生內存泄漏
我們來分析Java應用程序中的內存泄漏的解剖結構,以了解它們的來源,以及如何為他們做好準備,找到并修復它們。
1.什么是Java內存泄漏?
內存泄漏的標準定義是當對象不再被應用程序使用時發生的情況,但垃圾收集器無法將其從工作內存中刪除-因為它們仍被引用。因此,應用程序消耗越來越多的資源-這最終導致致命的OutOfMemoryError。
為了更好地理解這個概念,這里有一個簡單的視覺表示:
我們可以看到,我們有兩種類型的對象-引用和未引用;垃圾收集器可以刪除未引用的對象。引用的對象不會被收集,即使它們實際上不再被應用程序使用。
檢測內存泄漏可能很困難。許多工具執行靜態分析以確定潛在的泄漏,但這些技術并不完美,因為最重要的方面是運行系統的實際運行時行為。
因此,通過分析一些常見的情況,讓我們重點關注一些防止內存泄漏的標準做法。
Java堆漏洞
在這個初始部分中,我們將重點介紹經典的內存泄漏情況,其中Java對象不斷發布,不斷創建。
理解這些情況的有利技術是通過為堆設置較小的尺寸來使再現內存泄漏更容易。這就是為什么在開始我們的應用程序時,我們可以調整JVM以適應我們的內存需求:
這些參數指定了初始Java堆大小以及最大堆大小。
2.1。靜態場控制在對象引用
可能導致Java內存泄漏的第一種情況是引用具有靜態字段的重型對象。
我們來看一個簡單的例子:
我們創建了我們的ArrayList作為一個靜態字段-即使在完成了用于計算的JVM進程的生命周期之后,這些靜態字段也永遠不會被JVM垃圾收集器收集。我們還調用了Thread.sleep(10000)來允許GC執行完整的集合,并嘗試回收可以回收的所有內容。
我們來運行測試并使用我們的分析器分析JVM:
注意,一開始,所有的記憶當然是免費的。
然后,在短短2秒鐘內,迭代過程就會運行并完成-將所有內容加載到列表中(當然這取決于運行測試的機器)。
之后,觸發一個完整的垃圾收集循環,并繼續執行測試,以允許此循環時間運行并完成。如您所見,列表不被回收,并且內存消耗不會下降。
我們現在看到完全相同的例子,只有這一次,ArrayList不被靜態變量引用。相反,它是一個被創建,使用然后被丟棄的局部變量:
一旦該方法完成工作,我們將在下面的圖像上觀察主要的GC集合,大約第五十秒。
請注意,GC現在可以回收JVM使用的一些內存。
如何預防
現在您了解了這種情況,當然有辦法阻止它發生。
首先,我們需要密切關注靜電的使用;將任何集合或重對象聲明為靜態,將其生命周期與JVM本身的生命周期相關聯,并使整個對象圖形不可能收集。
我們還需要了解一般的集合-這是一種常用的方式,無意中持續參考超過我們需要的。
2.2。在LongString上調用String.intern()
經常導致內存泄漏的第二組方案涉及到字符串操作-特別是String.intern()API。
我們來看一個簡單的例子:
在這里,我們只是嘗試將大文本文件加載到運行的內存中,然后返回使用的規范表單。intern()。
該實習生API將會把STR在JVM的內存池字符串-它無法采集-又一次,這將導致GC是無法騰出足夠的內存:
我們可以清楚地看到,在第一個15秒JVM是穩定的,那么我們加載文件和JVM執行垃圾收集(第20秒)。
最后,調用了str.intern(),這會導致內存泄漏-表示高堆內存使用的穩定行,永遠不會被釋放。
如何預防
請記住,internedString對象存儲在PermGen空間中-如果我們的應用程序旨在對大字符串執行大量操作,我們可能需要增加永久代碼的大小:
第二個解決方案是使用Java8,其中PermGen空間由Metaspace替代,當在Strings上使用intern時不會導致任何OutOfMemoryError:
最后,還有幾個選項可以避免字符串上的.intern()API。
2.3。未封閉的流
忘記關閉流是一個非常常見的情況,當然,大多數開發人員可以關聯流。當自動關閉所有類型的流的能力被引入到try-with-resource子句中時,Java7中部分刪除了該問題。
為什么部分因為在嘗試,有資源的語法是可選的:
讓我們看一下從URL中加載大文件時應用程序的內存:
我們可以看到,堆的使用量隨著時間逐漸增加-這是由于不關閉流引起的內存泄漏的直接影響。
如何預防
我們總是需要記住手動關閉流,或者使用Java8中引入的自動關閉功能:
在這種情況下,BufferedReader將在try語句的結尾自動關閉,而不需要在顯式的finally塊中關閉它。
2.4。未關閉的連接
這種情況與前一種情況非常相似,處理未關閉連接(例如數據庫,FTP服務器等)的主要區別。再次,不正確的實施可能會造成很大的傷害,導致內存問題。
我們來看一個簡單的例子:
該URLConnection的仍然是開放的,其結果是,可以預見,內存泄漏:
注意垃圾收集器如何無法釋放未使用但引用的內存。情況在第一分鐘后立即清除-GC操作的數量迅速減少,導致堆內存使用增加,從而導致OutOfMemoryError。
如何預防
這里的答案很簡單-我們需要始終以嚴謹的方式關閉聯系。
2.5。將沒有hashCode()和equals()的對象添加到HashSet中
可能導致內存泄漏的一個簡單但非常常見的示例是使用HashSet與缺少其hashCode()或equals()實現的對象。
具體來說,當我們開始將重復的對象添加到一個集合中時,它只會增長,而不是忽略重復的對象。一旦添加,我們也將無法刪除這些對象。
我們創建一個沒有equals或者hashCode的簡單類:
現在,我們來看一下這個場景:
這個簡單的實現將導致運行時的以下情況:
注意垃圾收集器如何停止在大約1:40左右回收內存,并注意到內存泄漏;GC集合的數量立即下降了近四倍。
如何預防
在這些情況下,解決方案很簡單-提供hashCode()和equals()實現至關重要。
這里值得一提的工具是ProjectLombok-這通過注釋提供了大量的默認實現,例如@EqualsAndHashCode。
3.如何在您的應用程序中查找泄漏源
診斷內存泄漏是一個漫長的過程,需要大量的實踐經驗,調試技能和應用程序的詳細知識。
讓我們看看哪些技術可以幫助您,除了標準的分析。
3.1。詳細垃圾收集
識別內存泄漏的最快方法之一是啟用詳細的垃圾回收。
通過將-verbose:gc參數添加到我們應用程序的JVM配置中,我們啟用了一個非常詳細的GC跟蹤。匯總報告顯示在默認錯誤輸出文件中,這可以幫助您了解如何管理內存。
3.2。做剖析
第二種技術是我們在本文中使用的技術-這是分析。最流行的分析器是VisualVM-這是開始移動過去的命令行JDK工具并進入輕量級分析的好地方。
在本文中,我們使用另一個Profiler-YourKit-與VisualVM相比,它具有一些其他更高級的功能。
3.3。查看您的代碼
最后,這比處理內存泄漏的特定技術更為普遍的好做法。
簡單地說-仔細檢查您的代碼,定期進行代碼審查,并善用靜態分析工具,幫助您了解代碼和系統。
結論
在本教程中,我們對JVM中的內存泄漏情況進行了實際的了解。了解這些情況如何發生是處理這些情況的第一步。
然后,當發生泄漏時,使用技術和工具真正了解運行時發生的情況,這一點也是至關重要的。靜態分析和仔細的代碼重點審核只能做得如此之多,而且在一天結束的時候,運行時將會顯示代碼中不能立即識別的更復雜的泄漏。
最后,泄漏可能非常難以發現和復制,因為其中許多只發生在強烈的負載下,這通常發生在生產中。這就是您需要超越代碼級分析并在兩個主要方面進行工作-繁殖和早期檢測。
重現內存泄漏的最好和最可靠的方法是通過一套良好的性能測試來盡可能地模擬生產環境的使用模式。
和早期檢測是其中一個堅實的性能管理解決方案,甚至是早期檢測解決方案可以使顯著差異,因為它是有必要的洞察到生產應用程序的運行時的唯一途徑。
本教程的完整實現可以在GitHub上找到。這是一個基于Maven的項目,所以它可以簡單地導入和運行。
以上就是動力節點Java培訓機構小編介紹的“Java教程:在Java應用程序中發生內存泄漏”的內容,希望能夠幫助到大家,更多精彩內容請繼續關注動力節點Java培訓機構官網,每天會有精彩內容分享與你。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習