使用MAT分析OOM問題

OOM和內存泄漏在我們的工作中,算是相對比較容易出現的問題,一旦出現了這個問題,我們就需要對堆進行分析。

一般情況下,我們生產應用都會設置這樣的JVM參數,以便在出現OOM時,可以dump出堆內存文件,也就是保留案發現場,方便我們后續的研究。

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=.

至于分析堆內存的工具可以使用Jvisualvm,但Jvisualvm只能查看類使用內存的直方圖,無法有效的追蹤內存的引用關系,因此更加推薦使用Eclipse 的 Memory Analyzer(也叫做 MAT)做堆轉儲的分析。可以通過這個鏈接,下載 MAT。

使用MAT分析OOM問題,一般可以按照以下的思路進行:

  1. 通過支配樹功能或直方圖功能查看消耗內存最大的類型,來分析內存泄露的大概原因;
  2. 查看那些消耗內存最大的類型、詳細的對象明細列表,以及它們的引用鏈,來定位內存泄露的具體點;
  3. 配合查看對象屬性的功能,可以脫離源碼看到對象的各種屬性的值和依賴關系,幫助我們理清程序邏輯和參數;
  4. 輔助使用查看線程棧來看 OOM 問題是否和過多線程有關,甚至可以在線程棧看到 OOM 最后一刻出現異常的線程。

接下來,我們有一個案例,通過這個案例可以得到一個OOM后的堆轉儲文件java_pid12300.hprof,然后我們通過MAT的直方圖、支配樹、線程棧、OQL 等功能來分析此次 OOM 的原因。

在文章的最后會有代碼地址,運行代碼一段時間發生OOM后,你就可以得到一個hprof文件。

1、查看堆概述信息

通過MAT打開java_pid12300.hprof文件后,首先進入的是概覽信息界面。
在這里插入圖片描述
從這個概覽圖中,我們可以看出整個堆的大小是437.6MB。接下來我們可以通過直方圖來看這437.6MB的對象都是哪些對象。

2、直方圖觀察對象分布

點擊工具欄的第二個圖標,進入到直方圖視圖
在這里插入圖片描述
從直方圖中,我們可以看到,char[]字節數組占用的內存最多,對象數量給也最多。排名第二的String對象也很多,可以推斷程序可能是被String占滿了(String底層使用的就是char[]作為實際存儲,因此String多,char[]也會多)

3、分析char[]的引用關系

在 char[]上點擊右鍵,選擇 List objects->with incoming references,可以列出所有的char[]實例,以及每個 char[]的整個引用關系鏈:
在這里插入圖片描述
隨機展開一個 char[],如下圖所示:
在這里插入圖片描述

  • 在①處看到,這些 char[]幾乎都是 10000 個字符、占用 20000 字節左右(char 是 UTF-16,每一個字符占用 2 字節);
  • 在②處看到,char[]被 String 的 value 字段引用,說明 char[]來自字符串;
  • 在③處看到,String 被 ArrayList 的 elementData 字段引用,說明這些字符串加入了一個 ArrayList 中;
  • 在④處看到,ArrayList 又被 FooService 的 data 字段引用,這個 ArrayList 整個 RetainedHeap 列的值是 431MB。

Retained Heap(深堆)代表對象本身和對象關聯的對象占用的內存,Shallow Heap(淺堆)代表對象本身占用的內存。比如,我們的 FooService 中的 data 這個 ArrayList 對象本身只有 16 字節,但是其所有關聯的對象占用了 431MB 內存。這些就可以說明,肯定有哪里在不斷向這個 List 中添加 String 數據,導致了 OOM。

左側的藍色框可以查看每一個實例的內部屬性,圖中顯示 FooService 有一個 data 屬性,類型是 ArrayList。

如果我們希望看到字符串完整內容的話,可以右鍵選擇 Copy->Value,把值復制到剪貼板或保存到文件中:
在這里插入圖片描述
這里,我們復制出的是 10000 個字符 a(下圖紅色部分可以看到)。對于真實案例,查看大字符串、大數據的實際內容對于識別數據來源,有很大意義:
在這里插入圖片描述

4、利用支配樹查看內存中最大的對象

點擊工具欄的第三個按鈕可以進入到支配樹界面,這個界面會根據Retained Heap 倒序直接列出占用內存最大的對象。
在這里插入圖片描述
這樣我們就可以很快速的定位到是哪個對象導致的OOM,接下來我們就要看一下OOM的時候,FooService在執行什么邏輯。

5、查看線程視圖

點擊工具欄的第五個按鈕,打開線程視圖,首先看到的是main線程。
在這里插入圖片描述
從黑色框來看,確實這里發生了OOM。緊接繼續往下看,尋找我們可以的FooService,可以看到這個線程棧中FooSerice.oom()方法被調用。

在往下看的話,可看到參數中的 CommandLineRunner 你應該能想到,OOMApplication 其實是實現了 CommandLineRunner 接口,所以是 SpringBoot 應用程序啟動后執行的。

在FooService.oom()往上看,紅色框部分,我們可以猜測出這些字符串是由Stream操作產生的,以及在上面的StringBuilder 的 append是最終導致OOM的方法。

6、OQL查找類

最后我們還可以看一下FooService是不是Spring的Bean,又是不是單例?如果是的話,就更能確定是因為反復調用同一個 FooService 的 oom 方法,然后導致其內部的 ArrayList 不斷增加數據的。

我們可以點擊工具欄的第四個按鈕,進入到OQL界面,然后在這里我們可以使用類似 SQL 的語法,在 dump 中搜索數據(你可以直接在 MAT 幫助菜單搜索 OQL Syntax,來查看 OQL 的詳細語法)。

比如,輸入如下語句搜索 FooService 的實例:

select * from fcp.troubleshootingtools.mat.FooService

可以看到只有一個實例,然后我們通過 List objects 功能搜索引用 FooService 的對象:
在這里插入圖片描述
得到以下結果:
在這里插入圖片描述可以看到,一共兩處引用:

  • 第一處是,OOMApplication 使用了 FooService,這個我們已經知道了。
  • 第二處是一個 ConcurrentHashMap。可以看到,這個 HashMap 是 DefaultListableBeanFactory 的 singletonObjects 字段,可以證實 FooService 是 Spring 容器管理的單例的 Bean。

我們甚至可以在HashMap 上點擊右鍵,選擇 Java Collections->Hash Entries 功能,來查看其內容:
在這里插入圖片描述
在這里插入圖片描述
我們還可以在Value列通過正則進一步對解決進行過濾篩選:
在這里插入圖片描述
到現在為止,我們雖然沒看程序代碼,但是已經大概知道程序出現 OOM 的原因和大概的調用棧了。我們再貼出程序來對比一下,果然和我們看到得一模一樣:

@SpringBootApplication
public class OOMApplication implements CommandLineRunner {@AutowiredFooService fooService;public static void main(String[] args) {SpringApplication.run(OOMApplication.class, args);}@Overridepublic void run(String... args) throws Exception {//程序啟動后,不斷調用Fooservice.oom()方法while (true) {fooService.oom();}}
}
@Component
public class FooService {List<String> data = new ArrayList<>();public void oom() {//往同一個ArrayList中不斷加入大小為10KB的字符串data.add(IntStream.rangeClosed(1, 10_000).mapToObj(__ -> "a").collect(Collectors.joining("")));}
}

這邊做個小總結

  1. 我們通過MAT可以通過直方圖很方便的知道當前堆中哪個對象的數量較多且占據的堆內存較多。同時我們可以通過List objects查看引用鏈,最終定位到究竟是在哪個類中出現了大量對象導致OOM
  2. 除了直方圖外,我們可以使用支配樹在更快的時間發現導致OOM的對象
  3. 然后根據線程視圖,定位到具體是在哪個地方發生了OOM
  4. 最后呢,我們可以通過OQL查看類,搜索類有幾個實例,以及實例在哪幾個地方有引用

最后呢,可以到代碼地址中下載相關代碼,然后本地實踐一下。以及本篇文章的內容實際上是學習自極客時間的《Java業務開發常見錯誤100例》這是一個實戰性比較強的專欄,推薦大家也可以去看看

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/43237.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/43237.shtml
英文地址,請注明出處:http://en.pswp.cn/news/43237.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于libevent的tcp服務器

libevent使用教程_evutil_make_socket_nonblocking_易方達藍籌的博客-CSDN博客 一、準備 centos7下安裝libevent庫 yum install libevent yum install -y libevent-devel 二、代碼 server.cpp /** You need libevent2 to compile this piece of code Please see: http://li…

專訪 BlockPI:共建賬戶抽象未來的新一代 RPC 基礎設施

在傳統 RPC 服務板塊上&#xff0c;開發者一直飽受故障風險、運行環境混亂等難題的折磨。實現 RPC 服務的去中心化&#xff0c;且保持成本優勢和可擴展性&#xff0c;始終是區塊鏈基礎設施建設的重要命題之一。從 2018 年觀察中心化 RPC 供應商服務現狀開始&#xff0c;BlockPI…

內存管理(1)

內存管理&#xff08;1&#xff09; 1、各類型數據在內存中的存儲空間2、C內存管理方式2.1 針對于內置類型分析2.2 針對于自定義類型分析2.3 C語言與C在申請動態內存失敗時的區別 3、operator new 和 operator delete函數&#xff08;重點&#xff09;3.1 底層知識解析3.2 實現…

linux-shell腳本收集

創建同步腳本xsync mkdir -p /home/hadoop/bin && cd /home/hadoop/bin vim xsync#!/bin/bash#1. 判斷參數個數 if [ $# -lt 1 ] thenecho Not Arguementexit; fi#2. 遍歷集群所有機器 for host in node1 node2 node3 doecho $host #3. 遍歷所有目錄&#xff0c;挨…

web3:使用Docker-compose方式部署blockscout

最近做的項目,需要blockscout來部署一個區塊鏈瀏覽器,至于blockscout是什么,咱們稍后出一篇文章專門介紹下,本次就先介紹一下如何使用Docker-compose方式部署blockscout,以及過程中遇到的種種坑 目錄 先決條件我的環境準備工作Docker-compose1.安裝方式一:下載 Docker Co…

財務數據分析之現金流量表模板分享

現金流量表是我們常說的財務數據分析三表之一。它可以呈現一個企業的現金流情況&#xff0c;揭示企業經營管理健康狀態&#xff0c;但在實際使用中卻有總給人一種用不上、用不好的矛盾感。怎么才能把現金流量表做好&#xff1f;不如借鑒下大神的現金流量表模板。 下面介紹的是…

RabbitMQ-消息中間件學習記錄(what-how-why)

什么是消息中間件 簡單的來說就是消息隊列中間件&#xff0c;生產者發送消息到中間件&#xff0c;消息中間件用于 保存消息并發送消息到消費者。 消息中間件RabbitMQ的基本組件 1&#xff09;producer -生產者 2&#xff09;customer -消費者 3&#xff09;broker (經紀人)- M…

【Java 動態數據統計圖】動態數據統計思路案例(動態,排序,數組)四(116)

需求&#xff1a;&#xff1a;前端根據后端的返回數據&#xff1a;畫統計圖&#xff1b; 1.動態獲取地域數據以及數據中的平均值&#xff0c;按照平均值降序排序&#xff1b; 說明&#xff1a; X軸是動態的&#xff0c;有對應區域數據則展示&#xff1b; X軸 區域數據降序排序…

LabVIEW調用DLL傳遞結構體參數

LabVIEW 中調用動態庫接口時&#xff0c;如果是值傳遞的結構體&#xff0c;可以根據字段拆解為多個參數&#xff1b;如果參數為結構體指針&#xff0c;可用簇&#xff08;Cluster&#xff09;來匹配&#xff0c;其內存連續相當于單字節對齊。 1.值傳遞 接口定義&#xff1a; …

【FAQ】調用視頻匯聚平臺EasyCVR的iframe地址,視頻無法播放的原因排查

有用戶反饋&#xff0c;在調用iframe地址后嵌入用戶自己的前端頁面&#xff0c;視頻無法播放并且要求登錄。 安防監控視頻匯聚平臺EasyCVR基于云邊端一體化架構&#xff0c;具有強大的數據接入、處理及分發能力&#xff0c;可提供視頻監控直播、云端錄像、視頻云存儲、視頻集中…

視頻集中存儲EasyCVR視頻匯聚平臺定制項目增加AI智能算法

安防視頻集中存儲EasyCVR視頻匯聚平臺&#xff0c;可支持海量視頻的輕量化接入與匯聚管理。平臺能提供視頻存儲磁盤陣列、視頻監控直播、視頻輪播、視頻錄像、云存儲、回放與檢索、智能告警、服務器集群、語音對講、云臺控制、電子地圖、平臺級聯、H.265自動轉碼等功能。為了便…

【Unity每日一記】Physics.Raycast 相關_Unity中的“X光射線”

&#x1f468;?&#x1f4bb;個人主頁&#xff1a;元宇宙-秩沅 &#x1f468;?&#x1f4bb; hallo 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! &#x1f468;?&#x1f4bb; 本文由 秩沅 原創 &#x1f468;?&#x1f4bb; 收錄于專欄&#xff1a;uni…

05_bitmaphyperloglogGEO

Bitmap&hyperloglog&GEO 面試問 記錄對集合中的數據進行統計在移動應用中&#xff0c;需要統計每天的新增用戶數和第2天的留存用戶數&#xff1b;在電商網站的商品評論中&#xff0c;需要統計評論列表中的最新評論&#xff1a;在簽到打卡中&#xff0c;需要統計一個月內…

Python “貪吃蛇”游戲,在不斷改進中學習pygame編程

目錄 前言 改進過程一 增加提示信息 原版幫助摘要 pygame.draw pygame.font class Rect class Surface 改進過程二 增加顯示得分 改進過程三 增加背景景樂 增加提示音效 音樂切換 靜音切換 mixer.music.play 注意事項 原版幫助摘要 pygame.mixer pygame.mix…

kvm和vmware有什么區別?如何選擇?

一、kvm和vmware的區別 VMware vSphere 平臺 VMware 可以提供 ESXi 虛擬機監控程序和 vSphere 虛擬化平臺。VMware ESXi 是一個能夠直接安裝到物理服務器上的裸機虛擬機監控程序&#xff0c;可以幫你整合硬件。你可以用 VMware 的虛擬化技術來創建和部署虛擬機&#xff08;VM…

HTML詳解連載(7)

HTML詳解連載&#xff08;7&#xff09; 專欄鏈接 [link](http://t.csdn.cn/xF0H3)下面進行專欄介紹 開始嘍結構偽類選擇器作用 :nth-child&#xff08;公式&#xff09;作用舉例 偽元素選擇器作用注意&#xff1a; PxCoook作用盒子模型-重要組成部分 盒子模型-邊框線屬性名屬性…

excel中定位條件,excel中有哪些數據類型、excel常見錯誤值、查找與替換

一、如何定位條件 操作步驟&#xff1a;開始 - 查找和選擇 - 定位條件&#xff08;ctrl G 或 F5&#xff09; 注&#xff1a;如果F5不可用&#xff0c;可能是這個快捷鍵被占用了 案例&#xff1a;使用定位條件選擇取余中空單元格&#xff0c;填入100&#xff0c;按組合鍵ct…

【LeetCode75】第三十三題 二叉樹的最大深度

目錄 題目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代碼&#xff1a; 題目&#xff1a; 示例&#xff1a; 分析&#xff1a; 從這一題開始&#xff0c;LeetCode75進入到了二叉樹章節。 這邊建議不熟悉二叉樹的小伙伴可以先去做做力扣的前序遍歷&#xff0c;中序遍…

使用git rebase 之后的如何恢復到原始狀態

我們常常喜歡使用git rebase去切換分支提交代碼,操作流程就是: 先切換分支:比如當前是master 我們修改了一堆代碼產生一個commit id :5555555567777 那么我們常常比較懶就直接切換了:git checkout dev 然后呢?使用命令git rebase 5555555567777,想把這筆修改提交到d…

iPhone上的個人熱點丟失了怎么辦?如何修復iPhone上不見的個人熱點?

個人熱點功能可將我們的iPhone手機轉變為 Wi-Fi 熱點&#xff0c;有了Wi-Fi 熱點后就可以與附近的其他設備共享其互聯網連接。 一般情況下&#xff0c;個人熱點打開就可以使用&#xff0c;但也有部分用戶在升級系統或越獄后發現 iPhone 的個人熱點消失了。 iPhone上的個人熱點…