1. 程式人生 > >Java 堆記憶體溢位梗概分析

Java 堆記憶體溢位梗概分析

任何使用過基於 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 個使用者時可能會遭遇到記憶體峰值,從而導致記憶體溢位。有兩種可能的辦法去解決這個問題。

  • 洩露:由於某些程式設計問題,記憶體使用隨著時間的推移逐漸增加。

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

     

                            健康一段時間後,隨時間推移而洩露的圖表

          

                       引起記憶體使用凸起、導致記憶體溢位的記憶體圖表

在我們瞭解導致使用率激增的記憶體問題的本質之後,基於從對分析中得到的推斷,下面的這些方法或許可以用來避免遭遇記憶體溢位的錯誤。

解決記憶體問題

  1. 修復引起記憶體溢位的程式碼:由於應用在某段時間內增量添加了一個物件而沒有清除其引用(來自正在執行的應用程式的物件引用),導致不得不修復程式錯誤。例如,這一錯誤可能是插入了一個雜湊表, 其中的業務物件會逐漸增加,然而業務邏輯和事務在完成之後並沒有刪除這些物件。

  2. 增加記憶體最大值作為一種修復方法。在瞭解了執行記憶體特徵和堆之後,可能必須增加分配的最大堆記憶體來避免再次發生記憶體溢位,因為推薦的最大記憶體值不能夠滿足應用程式的穩定性。所以,應用程式可能不得不基於堆分析器的評估,將 Java -Xmx 的 flag 資訊更新成一個更高值後再來執行。

堆分析

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

使用 MAT 進行堆分析

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

1. 開啟記憶體溢位錯誤發生時產生的 .hprof 堆檔案。確保複製轉儲檔案到一個專門的資料夾下,因為 MAT 會建立許多索引檔案:檔案 -> 開啟

2. 開啟轉儲檔案,有記憶體洩漏嫌疑報告和元件報告的選項。選擇執行洩漏嫌疑報告。

Leak Suspect MAT

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 解析展示出有效的堆儲存。

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

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

如上面引用鏈顯示,例項 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 存留的記憶體。

總體講,一個物件的淺堆就是其在堆中的大小。同一個物件的保留大小就是當物件被垃圾回收時堆記憶體的總量。

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


相關推薦

Java 記憶體溢位梗概分析

任何使用過基於 Java 的企業級後端應用的軟體開發者都會遇到過這種低劣、奇怪的報錯,這些報錯來自於使用者或是測試工程師: java.lang.OutOfMemoryError:Java heap space。為了弄清楚問題,我們必須返回到演算法複雜性的電腦科學基礎,尤其是“

一次記憶體溢位問題分析——虛擬機器優化

開啟開發環境伺服器(我的伺服器應用是單獨部署的,幾乎沒有人訪問),偶然間看到命令視窗報異常,java.lang.OutOfMemoryError:heap space,還包括一大堆的其他錯誤——後面發現其他錯誤都是記憶體溢位引起的 用jconsole和jvisualvm嘗試開啟伺服器,行不通——堆記

Java常見記憶體溢位異常分析

http://www.importnew.com/14604.html http://blog.csdn.net/znb769525443/article/details/50853712 簡介 Java虛擬機器規範規定JVM的記憶體分為了好幾塊,比如堆,棧,程式計數

JVM之java記憶體溢位

Java堆用於儲存物件例項,只要不斷的建立物件,並且保證GC來不及清理java物件,那麼在物件數量達到最大堆的容量後就會產生堆記憶體溢位(堆記憶體大小可以通過 -Xms20M  -Xmx20M 來設定,最大堆和最小堆設定的一樣,可避免堆自動擴充套件)             

java關於記憶體溢位分析,mat工具

對於我們在現實的場景中,或多或少會遇到記憶體溢位的問題,可怎麼排查這個問題呢?首先我們需要了解一下關於java中自身安裝自帶的幾個命令:jmap,Jvisualvm,jstack等。一般情況下,對於記憶體,cpu,執行緒的監控用Jvisualvm這個命令我們很清晰的能檢視系統

java記憶體溢位了,教你一招必殺技

JAVA堆記憶體管理是影響效能主要因素之一。 堆記憶體溢位是JAVA專案非常常見的故障,在解決該問題之前,必須先了解下JAVA堆記憶體是怎麼工作的。 先看下JAVA堆記憶體是如何劃分的,如圖: JVM記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代(Young Gen

Java虛擬機器記憶體溢位原因分析以及解決方案

在Java虛擬機器規範的描述之中,除了程式計數器外,虛擬機器記憶體的其他幾個執行時區域都有可能發生記憶體溢位OutOfMemoryError(OOM)異常的可能。 最常見的記憶體溢位情況就是Java堆的記憶體溢位。明顯異常提示資訊為:Java heap Space.為了方便

寫程式分析記憶體溢位和棧記憶體溢位

打jar包 用命令java -jar  xxx.jar 寫程式分析堆記憶體溢位 package com.mvntest.mvn; import java.util.ArrayList; import java.util.List; public class HeapOO

jvm 03-java記憶體模型

java中最大的特點在於其具備良好的垃圾收集特性 GC是整個java之中最重要的安全保證 整個JVM中的GC的處理機制:對不需要的物件進行標記,而後進行清除 JVM堆記憶體劃分 在JDK1.8之後,將最初的永久帶記憶體空間取消了,該圖為JDK1.8

jvm記憶體溢位後,其他執行緒是否可繼續工作

    最近網上出現一個美團面試題:“一個執行緒OOM後,其他執行緒還能執行嗎?”。我看網上出現了很多不靠譜的答案。這道題其實很有難度,涉及的知識點有jvm記憶體分配、作用域、gc等,不是簡單的是與否的問題。     由於題目中給出的OOM,java中

04.Java 記憶體 新生代 老年代?

Java 中的堆是 JVM 所管理的最大的一塊記憶體空間,主要用於存放各種類的例項物件。 在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區域:Eden、F

Java服務記憶體OOM原因分析

1、出現問題的可能原因 對於應用來說記憶體分配太少 物件建立太多,又沒有釋放,造成記憶體洩漏嚴重,導致記憶體耗盡 申請太多的系統資源,系統資源耗盡。例如:不斷建立執行緒,不斷髮起網路連線 2、如何定位問題(可直接對dump檔案分析)      

Spark Heap OOM(記憶體溢位)

spark任務在除錯過程中,OOM是非常討厭的一種情況。本文針對Heap OOM的情況先做一定分析,告訴大家如何調參。 1.Heap OOM的現象 如果在Spark UI或者在spark.log中看到如下日誌: java.lang.OutOfMemoryError: G

Java記憶體溢位記憶體洩漏

記憶體溢位是啥?記憶體洩漏是啥?它們兩個有關係嗎?讓我們帶著上面的問題來看本篇文章 ·························································································

什麼情況下會發生記憶體溢位,棧記憶體溢位,結合例項說明

一、 棧溢位(StackOverflowError) 棧是執行緒私有的,他的生命週期與執行緒相同,每個方法在執行的時候都會建立一個棧幀,用來儲存區域性變量表,運算元棧,動態連結,方法出口燈資訊。區域性變量表又包含基本資料型別,物件引用型別(區域性變量表編譯器完成,執行期間不會

java常見記憶體溢位情形

虛擬機器棧溢位(如果虛擬機器在擴充套件時無法申請到足夠的記憶體空間將丟擲OutOfMemoryError)package com.jvm.memory; import java.util.ArrayList; import java.util.List; public

程式執行過程中記憶體的簡單分析

JAVA語言中除基本型別之外的變數型別,都稱之為引用型別。 JAVA中物件是通過引用reference對其操作的。 新建物件時,引用型別的資料都是null,基本資料型別 int 是 0 string 是null boolean 是false (預設初始化) 方法:引數傳遞的值是 值傳遞。

【JVM學習筆記】(一)jvm初體驗-記憶體溢位問題分析及解決方案

####1、開始 建立Main類和Demo類,在Main類的main方法中建立List,並向List中無限建立Demo物件,造成記憶體溢位, 並輸出記憶體溢位錯誤檔案在專案目錄下,為了使等待時間減小,設定執行堆記憶體大小。 ####2、建立Demo類 package com.ch

Java服務記憶體溢位問題解決和總結

最近,公司測試環境服務發現一個問題:一個介面服務,合作方再調介面時,經常會出現連線超時異常(connection reset by peer),緊接著看到服務記憶體100%,加記憶體也沒用,不管加多少還是會緩慢升至100%。如下圖: 通過各位大神的指點迷津,大概定位到以

java記憶體和棧記憶體的區別

一段時間之前,我寫了兩篇文章文章分別是Java的垃圾回收和Java的值傳遞,從那之後我收到了很多要求解釋Java堆記憶體和棧記憶體的郵件,並且要求解釋他們的異同點。在Java中你會看到很多堆和棧記憶體的引用,JavaEE書和文章很難在程式的角度完全解釋什麼是堆什麼是棧。Jav