首页 > 其他分享 >一次通过dump文件分析OutOfMemoryError异常代码定位过程

一次通过dump文件分析OutOfMemoryError异常代码定位过程

时间:2024-05-06 18:23:14浏览次数:25  
标签:文件 Java MAT dump 代码 转储 内存 OutOfMemoryError

OutOfMemoryError是Java程序中常见的异常,通常出现在内存不足时,导致程序无法运行。

当出现OutOfMemoryError异常时,可能的现象是这样的。

  • 程序异常终止:OutOfMemoryError 通常会导致程序异常终止。JVM 无法为新对象分配内存时,会抛出该异常。
  • 堆内存不足:OutOfMemoryError 表示堆内存不足以为新对象分配空间。这可能会导致应用程序无法继续正常运行。
  • 内存泄漏:OutOfMemoryError 有时会暗示存在内存泄漏问题。即使没有明显的内存泄漏,也可能是应用程序中某些对象持续增加,导致堆空间耗尽。
  • 堆转储文件:在抛出 OutOfMemoryError 异常时,JVM 可能会生成一个堆转储文件(heap dump),记录当前堆内存的状态。可以使用该文件来分析内存使用情况和定位问题。
  • 性能下降:在出现内存不足的情况下,应用程序可能会经历性能下降,因为 JVM 可能会频繁执行垃圾回收以尝试释放内存。
  • 日志记录: 日志文件中发现 OutOfMemoryError 。异常消息通常会包含一些有关内存分配失败的信息,例如 "Java heap space"(堆空间不足)或 "GC overhead limit exceeded"(垃圾回收开销过大)。
  • 程序假死:当 JVM 的堆空间不足以分配新对象时,可能会触发垃圾回收。如果垃圾回收器尝试回收内存但无法释放足够的空间,或者由于频繁的垃圾回收导致系统资源被耗尽,程序可能会出现假死状态。表现为进程还在,但是无响应、长时间停顿。

可能的堆栈信息是这样的。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at demo.OOMDemo.main(OOMDemo.java:22)

借助MAT工具和内存泄漏产生的dump文件可以分析可能的内存泄漏代码问题定位。

什么是OutOfMemoryError异常

在 Java 中,OutOfMemoryError 是一种错误(Error),而不是异常(Exception)。

它表示 Java 虚拟机(JVM)已经耗尽了可用的内存资源,无法再分配给新的对象,导致程序无法继续执行。

OutOfMemoryError 可能由以下几种情况引起:

  • 堆内存溢出(Heap Space):当 Java 程序中创建了太多的对象,而堆内存无法满足这些对象的需求时,就会发生堆内存溢出。这通常是因为程序中存在内存泄漏(Memory Leak)或者处理大量数据时没有及时释放内存导致的。
  • 方法区溢出(PermGen Space 或 Metaspace):Java 虚拟机中的方法区用于存储类的元数据信息、静态变量、常量池等数据。当加载的类过多或者字符串常量过多时,方法区可能会溢出。在 Java 8 及之前的版本中使用的是 PermGen Space(永久代),而在 Java 8 及之后的版本中使用的是 Metaspace。溢出时会抛出相应的错误:PermGen space 或 Metaspace。
  • 栈溢出(Stack Overflow):每个线程在 Java 虚拟机中都有自己的栈空间,用于存储方法的调用栈信息。当递归调用层级过深或者方法调用过多时,栈空间可能会溢出,导致栈溢出错误。
  • 直接内存溢出:使用 NIO(New Input/Output)库进行 IO 操作时,可能会使用到直接内存(Direct Memory)。如果程序中频繁申请直接内存而没有及时释放,可能会导致直接内存溢出。

什么是dump文件

在 Java 中,Dump 文件是指在程序发生严重问题(比如崩溃或者出现内存溢出等)时,用于记录当前 JVM 运行状态的文件。Dump 文件可以包含有关 JVM 运行时的诊断信息,例如内存使用情况、线程堆栈信息、对象实例信息等,有助于开发人员分析问题并定位 bug。

通常情况下,Dump 文件主要用于以下几种情况:

  • 内存溢出(OutOfMemoryError)问题分析:当程序发生内存溢出错误时,可以生成 Dump 文件以便后续分析。Dump 文件中包含了内存堆的快照,可以查看堆中对象的分布情况,帮助开发人员找出造成内存溢出的原因。
  • JVM 崩溃问题分析:当 JVM 运行时发生崩溃,无法正常工作时,可以生成 Dump 文件以便排查问题。Dump 文件中包含了 JVM 运行时的状态信息,例如线程状态、堆栈信息等,有助于分析问题的根本原因。
  • 性能调优和分析:在进行性能调优时,Dump 文件可以提供有关 JVM 运行时的详细信息,例如线程的 CPU 占用情况、内存使用情况等,有助于分析程序的瓶颈并进行优化。

生成 Dump 文件通常需要使用 JVM 提供的工具或者命令行参数。例如,可以使用以下 JVM 参数来指定在发生 OutOfMemoryError 时生成 Dump 文件:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.log

生成的 Dump 文件通常是二进制格式的文件,可以使用专门的工具(如 Eclipse Memory Analyzer)来打开和分析。

通过分析 Dump 文件,开发人员可以更好地理解程序的运行情况,并找出问题所在。

什么是MAT工具

MAT(Memory Analyzer Tool)是一个用于 Java 应用程序内存分析的强大工具。它是一个开源项目,由 Eclipse 基金会提供支持。MAT 的主要功能是帮助开发人员分析 Java 程序的内存使用情况,特别是用于识别和解决内存泄漏问题。

MAT 工具可以帮助开发人员解决以下类型的问题:

  • 内存泄漏分析:MAT 可以通过分析 Java 堆转储(Heap Dump)文件来识别内存泄漏问题。它可以显示对象实例之间的引用关系,并帮助开发人员找出未被正确释放的对象,从而定位内存泄漏的根本原因。
  • 内存使用情况分析:MAT 可以提供详细的内存使用情况报告,包括对象实例数量、对象大小、对象引用关系等信息。这有助于开发人员了解 Java 应用程序的内存使用模式,并进行优化。
  • GC 日志分析:MAT 可以分析 JVM 输出的垃圾回收(GC)日志文件,帮助开发人员了解 GC 活动的情况,包括 GC 频率、停顿时间、对象分配速率等信息。
  • 线程分析:MAT 可以提供线程转储(Thread Dump)文件的分析,帮助开发人员识别死锁、线程阻塞等问题,并定位问题的原因。

MAT 提供了一个直观的用户界面,可以通过图形化界面进行内存分析和问题定位。它还提供了一系列的分析工具和报告,帮助开发人员深入理解 Java 应用程序的内存行为。

搜索引擎搜索 Eclipse Memory Analyzer Tool可以找到下载链接。(外链审核很严格~~)

异常发生了定位异常代码

使用 MAT 定位 OutOfMemoryError(OOM)的过程通常包括以下步骤:

收集堆转储文件:首先,需要在发生 OutOfMemoryError 异常时收集 Java 应用程序的堆转储文件。可以通过在 JVM 启动参数中添加 -XX:+HeapDumpOnOutOfMemoryError 来实现,在发生 OOM 异常时会自动生成堆转储文件。

打开 MAT 工具:打开 Memory Analyzer Tool(MAT)工具,并导入之前收集到的堆转储文件。通常,堆转储文件的格式是 .hprof

执行内存分析:在 MAT 中,可以执行各种内存分析操作,以定位导致 OutOfMemoryError 异常的原因。以下是一些常见的分析步骤:

  • 内存泄漏分析:使用 MAT 的 Leak Suspects 或 Dominator Tree 功能来查找可能导致内存泄漏的对象或对象组。这些功能会显示对象实例之间的引用关系,帮助确定哪些对象未被正确释放。
  • 对象分布分析:查看对象分布报告,了解不同类型的对象在堆中的分布情况。这有助于确定哪些类型的对象占用了大量的内存空间。
  • 最大对象分析:使用 Histogram 功能查看堆中最大的对象实例,这些对象可能是导致内存问题的主要原因。
  • 执行代码路径分析:如果堆转储文件包含了足够的信息,MAT 可以尝试生成代码路径以帮助确定哪些代码路径导致了内存问题。

定位异常代码:在进行内存分析的过程中,可以尝试定位导致 OutOfMemoryError 异常的相关代码。根据分析结果,可以查看对象的引用关系,确定哪些代码路径导致了内存泄漏或者内存消耗过大的问题。

异常没有发生定位异常代码

异常没有发生定位异常代码,需要通过jmap生成dump文件。

然后将其导入到 MAT 中进行分析。以下是生成堆转储文件的步骤:

  • 确定 Java 进程 ID:首先,需要确定正在运行的 Java 进程的进程 ID(PID)。可以使用 jps 命令查看正在运行的 Java 进程及其 PID。
  • 生成堆转储文件:使用 jmap 命令生成堆转储文件。命令格式如下:
jmap -dump:file=<文件路径> <PID>

例如,要生成名为 heapdump.hprof 的堆转储文件,可以执行以下命令:

jmap -dump:file=heapdump.hprof <PID>

这将在当前工作目录下生成一个名为 heapdump.hprof 的堆转储文件。

  • 导入堆转储文件到 MAT:将生成的堆转储文件导入到 MAT 中进行分析。打开 MAT,然后选择 File -> Open Heap Dump,然后选择生成的堆转储文件。
  • 执行内存分析:一旦堆转储文件被导入到 MAT 中,就可以执行内存分析,按照前面提到的步骤来查找内存问题。

通过这些步骤可以手动生成堆转储文件并使用 MAT 进行分析,即使没有在 OutOfMemoryError 发生时自动生成堆转储文件也可以找到问题所在。

验证demo

首先通过一段测试代码来模拟OutOfMemoryError异常。


import java.util.ArrayList;
import java.util.List;

/**
 * 用于验证oom异常
 * jvm启动参数  -Xmx200m -Xms200m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof
 *
 * @author nine
 * @since 1.0
 */
public class OOMDemo {

    public static void main(String[] args) {
        List<Object> listMock = new ArrayList<>();
        List<Object> list = new ArrayList<>();
        while (true) {
            // 此处代码用于创造oom错误
            list.add(new byte[10]);
            // 此处代码是干扰代码,因为清空了变量不会内存泄漏
            listMock.add(new byte[5]);
            listMock.clear();
        }
    }
}

启动程序运行,增加jvm参数 -Xmx200m -Xms200m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof。其中堆内存大小为200M,便于复现问题。

等待一段时间后,程序会抛出OutOfMemoryError异常。

java.lang.OutOfMemoryError: Java heap space
Dumping heap to heapdump.hprof ...
Heap dump file created [212763268 bytes in 0.572 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at demo.OOMDemo.main(OOMDemo.java:20)

同时可以在classpath下看到heapdump.hprof堆转储文件。

打开MAT,选择 File>Open Heap Dump>选择heapdump.hprof>Leak Suspects Report

MAT会分析可能的几个问题,标题是 Problem Suspect 1等

由于此处只有一段代码,分析出来的问题也就一个可能问题。

The thread java.lang.Thread @ 0xf45310d0 main keeps local variables with total size 204,667,384 (98.35%) bytes.
The memory is accumulated in one instance of “java.lang.Object[]”, loaded by “<system class loader>”, which occupies 204,666,704 (98.35%) bytes.
Significant stack frames and local variables
•demo.OOMDemo.main([Ljava/lang/String;)V (OOMDemo.java:20)◦java.util.ArrayList @ 0xf45930a8 retains 204,666,728 (98.35%) bytes


The stacktrace of this Thread is available. See stacktrace. See stacktrace with involved local variables.

Keywords
java.lang.Object[]
demo.OOMDemo.main([Ljava/lang/String;)V
OOMDemo.java:20

Details »

点击See stacktrace链接可以看到堆栈信息。

main
  at java.lang.OutOfMemoryError.<init>()V (OutOfMemoryError.java:48)
  at demo.OOMDemo.main([Ljava/lang/String;)V (OOMDemo.java:20)

这也就是发生异常的代码位置。通过修改第20行代码,将list.add(new byte[10])注释掉,可以发现oom错误消失。

注:一般堆转储文件很大,可能需要mat的启动参数来进行大文件分析。

# 打开 MemoryAnalyzer.ini 文件
# 修改启动参数为 -Xmx2048m
-startup
plugins/org.eclipse.equinox.launcher_1.6.600.v20231106-1826.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.2.800.v20231003-1442
-vmargs
--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
-Xmx2048m

发生OutOfMemoryError的解决办法

解决 OutOfMemoryError 异常的方法取决于具体情况和根本原因。

  • 分析堆转储文件:当发生 OutOfMemoryError 异常时,可以生成堆转储文件,通过分析该文件来定位内存泄漏或者内存使用过多的原因。
  • 优化代码:检查代码中是否存在内存泄漏或者不必要的对象持有,优化数据结构和算法以减少内存使用量。特别是要注意避免在循环中创建大量临时对象,及时释放不再需要的对象引用。
  • 检查第三方库:某些第三方库可能存在内存泄漏或者内存占用过大的问题,需要对其进行检查和优化,或者考虑更换其他库。
  • 使用更高效的数据结构和算法:选择更适合场景的数据结构和算法,以减少内存使用量和提高性能。
  • 分析内存使用情况:定期监控应用程序的内存使用情况,及时发现潜在的问题并采取相应措施。
  • 使用更轻量级的解决方案:有时可以考虑使用更轻量级的框架或工具,以减少内存消耗。

再者可以优化内存参数:

  • 增加堆内存:通过增加 JVM 的堆内存大小来提供更多的内存空间。可以通过调整 -Xmx-Xms 参数来增加堆内存的最大和初始大小。但需要注意,过大的堆内存可能会导致垃圾回收时间过长,影响程序性能。
  • 增加物理内存:如果是物理机器内存不足导致的 OutOfMemoryError,可以考虑增加物理内存来解决问题。

关于作者

来自一线全栈程序员nine的探索与实践,持续迭代中。

欢迎关注或者点个小红心~

标签:文件,Java,MAT,dump,代码,转储,内存,OutOfMemoryError
From: https://www.cnblogs.com/r0ad/p/18175575

相关文章

  • 主页和一个详情页完全成功(5个代码)(样式,有动画)
    C文件夹布局网页存放/│├──app.py#Flask应用├──templates/#存放HTML文件│├──index.html#主页│└──upload.html#详情页├──static/│├──css/││└──styles.css......
  • top k 问题 Java解决代码
    topk问题:从10亿个数中选出最大的1万个数,处理方式:用小顶堆,先用1万个数建立小顶堆,再把剩余数从小顶堆里过一遍,每次与堆顶元素比较,小顶堆的堆顶元素是最小的,如果比堆顶元素大就替换堆顶元素,重新生成小顶堆,继续比较直到10亿条数据比完,堆里剩下的就是最大的1万个数。如果是从大量元素......
  • 《代码随想录》-2.二分查找
    前提:1.有序2.无重复//版本1intleft=0;intright=nums.size()-1;while(left<=right){intmiddle=left+(right-left)/2;//防止溢出if(nums[middle]>target){right=milddle-1;}elseif(nums[middle]<target{left=middle+1;}else{returnmiddle;......
  • 低代码优于无代码?
    从1804年打孔式编程出现,编程语言至今已经存在了200多年。而从50年代以来,新的编程语言也不断涌现,现在已经有250多种了。这就意味着,开发人员最需要习惯的事情就是不断改变。 编程界最近的一个变化是集成开发环境(IDE)——软件应用程序,一般包括代码编辑器、编译器、调试器和图形用......
  • Git仓库代码地址更改后,如何将已经拉到本地的项目提交到新的Git仓库
    Git仓库代码地址更改后,如何将已经拉到本地的项目提交到新的Git仓库 一、背景介绍远程开发过程中,可能会需要支持外网环境下访问Git代码地址,但是如果处于公司内网环境,需要切换到内网环境进行提交和更新代码。 二、操作步骤1、进入项目终端或者IntelliJIDEA的Terminal查看......
  • 《代码随想录》-1.数组理论基础
    特点:1.内存空间-连续存放——>增删元素麻烦2.数据-相同类型3.下标从0开始注意:数组的元素采用覆盖的形式二维数组在内存的空间地址:1.C++中二维数组在地址空间上是连续的2.Java中二维数组每一行的头节点的地址是没有规则的......
  • 【学习笔记】初次学习斜率优化的代码及笔记
    include<bits/stdc++.h>usingnamespacestd;intn,m;intdp[10000],s[6100],q[10000];intslope(intj,intk){intx=(dp[j]-dp[k]+s[j]s[j]-s[k]s[k])/(s[j]+s[k]);returnx;}//求斜率intmain(){while(scanf("%d%d",&n,&m)!=EOF){for(in......
  • Jmeter内存溢出:java.lang.OutOfMemoryError: Java heap space解决思路
    一、问题原因用JMeter压测,有时候当模拟并发请求较大或者脚本运行时间较长时,JMeter会停止,报OOM(内存溢出)错误。原因是JMeter是一个纯Java开发的工具,内存由java虚拟机JVM管理,当内存回收不及时,堆内存不足时,就会报内存溢错误。概念补充:内存泄露:应用使用资源之后没有及时释放,导致应......
  • 使用 SSH 转义代码来控制连接
    OpenSSH最常被忽视的一个非常有用的功能是能够从连接内部控制会话的某些方面。通过使用SSH转义代码,我们能够在会话内部与本地SSH软件进行交互。强制从客户端断开连接(如何退出卡住或冻结的会话)这些命令可以在SSH会话中以~控制字符开头执行。只有在换行后第一次键入时才......
  • LLM2Vec介绍和将Llama 3转换为嵌入模型代码示例
    嵌入模型是大型语言模型检索增强生成(RAG)的关键组成部分。它们对知识库和用户编写的查询进行编码。使用与LLM相同领域的训练或微调的嵌入模型可以显著改进RAG系统。然而,寻找或训练这样的嵌入模型往往是一项困难的任务,因为领域内的数据通常是稀缺的。但是这篇论文LLM2Vec,可以将......