中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java 堆內存溢出原因分析

發布時間:2020-08-24 06:30:27 來源:腳本之家 閱讀:251 作者:dreamanzhao, 溪邊九節 欄目:編程語言

前言

任何使用過基于 Java 的企業級后端應用的軟件開發者都會遇到過這種低劣、奇怪的報錯,這些報錯來自于用戶或是測試工程師: java.lang.OutOfMemoryError:Java heap space。

為了弄清楚問題,我們必須返回到算法復雜性的計算機科學基礎,尤其是“空間”復雜性。如果我們回憶,每一個應用都有一個最壞情況特征。具體來說,在存儲維度方面,超過推薦的存儲將會被分配到應用程序上,這是不可預測但尖銳的問題。這導致了堆內存的過度使用,因此出現了"內存不夠"的情況。

這種特定情況最糟糕的部分是應用程序不能修復,并且將崩潰。任何重啟應用的嘗試 - 甚至使用最大內存(-Xmx option)- 都不是長久之計。如果不明白什么導致了堆使用的膨脹或突出,內存使用穩定性(即應用穩定性)就不能保障。于是,什么才是更有效的理解關于內存的編程問題的途徑?當內存溢出時,明白應用程序的內存堆和分布情況才能回答這個問題。

在這一前提下,我們將聚焦以下方面:

  • 當內存溢出時,獲取到 Java 進程中的堆轉儲。
  • 明白應用程序正在遭遇的內存問題的類型。
  • 使用一個堆分析器,可以使用 Eclipse MAT 這個優秀的開源項目來分析內存溢出的問題。

配置應用,為堆分析做準備

任何像內存溢出這種非確定性的、時有時無的問題對于事后的分析都是一個挑戰。所以,最好的處理內存溢出的方法是讓 JVM 虛擬機轉儲一份 JVM 虛擬機內存狀態的堆文件。

Sun HotSpot JVM 有一種方法可以引導 JVM 轉儲內存溢出時的堆狀態到一個文件中。其標準格式為 .hprof 。所以,為了實現這種操作,向 JVM 啟動項中添加 XX:+HeapDumpOnOutOfMemoryError 。因為內存溢出可能經過很長一段時間才會發生,向生產系統增加這一選項也是必須的。

如果堆轉儲 .hprof 文件必須被寫在一個特定的文件系統位置,那么就添加目錄途徑到 XX:HeapDumpPath 。只需確保該應用對于指定目錄途徑始終擁有寫入權限。

原因分析

101:了解內存溢出錯誤的本質

當嘗試去評估和了解一個內存溢出錯誤時,最先做的事情應該是觀察內存增長特征。根據情況做出可能性的評估:

  • 尖峰狀:這種類型的內存溢出在某種類型的加載上會是比較激烈的。當 JVM 分配內存給 20 個用戶時,應用程序可以正常運行。但是,如果到第 100 個用戶時可能會遭遇到內存峰值,從而導致內存溢出。有兩種可能的辦法去解決這個問題。
  • 泄露:由于某些編程問題,內存使用隨著時間的推移逐漸增加。

Java 堆內存溢出原因分析

擁有良性垃圾回收機制的健康圖表

Java 堆內存溢出原因分析

健康一段時間后,隨時間推移而泄露的圖表

Java 堆內存溢出原因分析

引起內存使用凸起、導致內存溢出的內存圖表

在我們了解導致使用率激增的內存問題的本質之后,基于從對分析中得到的推斷,下面的這些方法或許可以用來避免遭遇內存溢出的錯誤。

解決內存問題

1.修復引起內存溢出的代碼:由于應用在某段時間內增量添加了一個對象而沒有清除其引用(來自正在運行的應用程序的對象引用),導致不得不修復程序錯誤。例如,這一錯誤可能是插入了一個哈希表, 其中的業務對象會逐漸增加,然而業務邏輯和事務在完成之后并沒有刪除這些對象。

2.增加內存最大值作為一種修復方法。在了解了運行內存特征和堆之后,可能必須增加分配的最大堆內存來避免再次發生內存溢出,因為推薦的最大內存值不能夠滿足應用程序的穩定性。所以,應用程序可能不得不基于堆分析器的評估,將 Java -Xmx 的 flag 信息更新成一個更高值后再來運行。

堆分析

下面我們將詳細分析如何使用一個堆分析工具來分析堆轉儲。在示例中,將使用到 Eclipse 基金會的開源工具 MAT 。

使用 MAT 進行堆分析

是時候進行深入探討了。我們將通過一系列的步驟,幫助探索在 MAT 中的不同表現和視圖,以獲取一個堆內存溢出的示例并思考分析。

1. 打開內存溢出錯誤發生時產生的 .hprof 堆文件。確保復制轉儲文件到一個專門的文件夾下,因為 MAT 會創建許多索引文件:文件 -> 打開

2. 打開轉儲文件,有內存泄漏嫌疑報告和組件報告的選項。選擇運行泄漏嫌疑報告。

Java 堆內存溢出原因分析

3. 泄漏嫌疑表打開后,在預覽窗口的餅狀圖會展示在每個對象基礎上保留內存的分布情況。它顯示了內存中的最大對象(擁有最高保留內存的對象 —— 累積的內存和引用的對象)。

4. 上面的餅圖通過聚合擁有最高內存引用(本身內存和總內存)的對象來展示 3 個問題嫌疑人。

讓我們逐一分情況查看,評估它是否是內存溢出錯誤的根本原因。

可疑點 1

由 “<system class loader>” 加載的 454,570 個 “java.lang.ref.Finalizer” 實例占用了 790,205,576(47.96%)個字節。

這就是告訴我們有 454,570 個 JVM finalizer(終結器)實例占據了分配的應用內存的近 50 %。

假設讀者知道 Java Finalizer 是做什么的,上面的信息會讓我們明白什么呢?

本質上,開發者編寫了一些定制化的終結器去釋放一個實例的資源。這些由終結器收集的實例不在 JVM 使用單獨隊列的垃圾回收算法的范圍之內。實際上,這種途徑比起垃圾回收機制的清理路徑更長。所以現在我們應該努力搞清楚這些終結器到底終結了什么?

也或許是可疑點 2 ,占據了 20% 的 sun.security.ssl.SSLSocketImpl 。我們能確認是否這些就是要被終結器終結的實例嗎?

可疑點 2

現在,讓我們打開在 MAT 頂部的工具按鈕下面的 Dominator 視圖。我們會看到所有的列出的類實例,經由 MAT 解析展示出有效的堆存儲。

Java 堆內存溢出原因分析

下一步,在 Dominator 視圖,我們嘗試理解 java.lang.Finalizer 和 sun.security.ssl.SSLSocketImpl 之間的關系。我們右鍵點擊 sun.security.ssl.SSLSocketImpl 這一列,打開 GC Roots -> exclude soft/weak references。

Java 堆內存溢出原因分析

現在,MAT 將會開始繪制內存的圖表來顯示 GC root 的路徑以及它所對應的實例引用。這會被顯示在另外一個頁面上,顯示的引用如下:

Java 堆內存溢出原因分析

如上面引用鏈顯示,實例 SSLSocketImpl 來自于 java.lang.ref.Finalizer,整個 SSLSocketImpl 實例大約占用了 88k。我們還注意到 finalizer 鏈是一個針鏈表數據結構它指向下一個實例。

推論:在這一點上,我們有一個明確的感覺,Java finalizer 試圖在收集 SSLSocketImpl 對象。為了解釋為什么還有很多信息沒有被收集到,我開始檢查代碼。

檢查代碼

代碼檢查需要查看是不是由 socket 套接字被關閉導致的。在這種情況下,它顯示與 I/O 相關的所有流,需要被正確地關閉。在一點上,我們懷疑 JVM 是始作俑者。實際上,在 Open JDK 6.0.XX 的 GC(垃圾收集器)上的代碼中有一個 BUG。

我希望這篇文章給你一個模式來分析 Java 應用中的錯誤是由堆存儲還是內部問題導致的。希望你使用堆分析愉快!

Shallow heap (淺堆) vs. Retained Heap (保留堆)

淺堆是一個對象消耗的內存。根據情況,一個對象需要 32 位或 64 位(取決于其操作系統架構),對于整型為 4 字節,對于 Long 型為 8 字節等等。依據堆轉儲格式,其內存大小(比如,向 8 對齊)或許適應于更好地塑造虛擬機的真實消耗。

X 的保留集合是當 X 被垃圾回收時,那些將要被移除的對象集合。

X 的保留堆是在 X 的保留集合中所有對象的淺堆之和,也就是 X 存留的內存。

總體講,一個對象的淺堆就是其在堆中的大小。同一個對象的保留大小就是當對象被垃圾回收時堆內存的總量。

一些對象的主要集合,比如某一特定類的所有對象、或是由某一特定類加載器加載的所有類的所有對象、或僅僅是一些任意的對象,它們的保留集是如果那些主要集的所有對象變得不可接近時所釋放的對象集。保留集包括這些對象和僅通過這些對象才能獲取的其它對象。保留集的大小是包含在保留集中的所有對象的堆的大小。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

台南县| 徐州市| 金川县| 宣威市| 瑞安市| 宁海县| 平遥县| 公主岭市| 大兴区| 石家庄市| 关岭| 安多县| 富民县| 闸北区| 沙河市| 怀柔区| 荆门市| 正定县| 鹤峰县| 北京市| 横山县| 文化| 都昌县| 拉孜县| 临西县| 嵊泗县| 怀化市| 祥云县| 谢通门县| 大连市| 福清市| 南岸区| 利川市| 华坪县| 石阡县| 巴楚县| 罗城| 大姚县| 观塘区| 鄂温| 克拉玛依市|