Java的dump文件分析及JProfiler使用

1 dump文件介绍

从软件开发的角度上,dump文件就是当程序产生异常时,用来记录当时的程序状态信息(例如堆栈的状态),用于程序开发定位问题。

idea配置发生OOM的时候指定路径生成dump文件

# 指定发生OOM异常的时候,在d盘下生成对应的dump文件-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\

2 JProfiler介绍

2.1 下载

JProfiler下载:
链接:https://pan.baidu.com/s/1WXCc4FMOC3QQtjkhY4Qeow
提取码:5xrm
版本:JProfiler 12.0.4

2.2 与idea集成

  1. 本地windows下载并安装好JProfiler

  2. idea安装JProfiler插件

  3. 指定本地windows的JProfiler路径[settings – tools]


4. 点击图标启动,JProfiler就默认监控到了指定Java程序

2.3 基本使用

①JProfiler基本参数

在概览页我们可以清晰的看到内存使用量、垃圾收集活动、类加载数量、线程个数和状态、CPU 使用率等指标随时间变化的趋势。


通过此图,我们可以作出如下基本判断:

  • 程序在运行过程中会产生大量对象,但这些对象生命周期极短,大部分都能被垃圾收集器及时回收,不会造成内存无限增长。
  • 加载类的数量在程序初始时增长较快,随后保持平稳,符合预期。
  • 在程序运行过程中,有大量线程处于阻塞状态,需要重点关注。
  • 在程序刚启动时,CPU 使用率较高,需要进一步探究其原因。
②测试分析dump文件
  1. 模拟OOM
public class JProfilerTest {public static void main(String[] args) throws InterruptedException {List<byte[]> list = new ArrayList<>();while (true) {byte[] bytes = new byte[1024 * 1024 * 50];list.add(bytes);TimeUnit.SECONDS.sleep(1);}}}
  1. 程序添加VM Options
# 监控OOM,发生OOM之后指定dump文件生成到d:\-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\

  1. JProfiler方式启动程序,观察控制台打印

程序运行一段时间之后,发生OOM异常

5. 查看dump文件,用JProfiler打开

6. 分析dump文件

查看最大对象内部结构

可以发现是改list中含有了太多的byte[]数组

ps:其他查看方法类似

3 常见JVM问题

3.1 OOM

①堆溢出

原因:

1. 无法在Java堆中分配对象2. 应用程序保存了无法被GC回收的对象3. 程序过度使用finalizer

排查思路:

  1. 查看关键报错信息
  2. 使用内存映像分析工具(MAT或JProfiler)分析dump文件,分析是内存泄漏还是内存溢出
  3. 如果是内存泄漏,通过工具查看泄漏对象到GC Roots引用链,修复内存泄漏
  4. 如果不是,检查代码是否有死循环,递归等,再考虑用-Xmx增加堆大小

demo代码JVM配置参数:

  • -Xms20m JVM初始分配的内存20m
  • -Xmx20m JVM最大可用内存为20m
  • -XX:+HeapDumpOnOutOfMemoryError 当JVM发生OOM时,自动生成DUMP文件
  • -XX:HeapDumpPath=/Users/mytest/Desktop/dump/ 生成DUMP文件的路径
②栈溢出

栈:虚拟机栈和本地方法栈,关于栈,Java虚拟机规范中描述了两种异常:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OOM:如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出

原因:

1. 单个线程下,栈帧太大或虚拟机栈容量太小,内存无法分配2. 不断建立线程

排查思路:

  • 查看关键报错信息,确定是StackOverflow还是OOM
  • 如果是StackOverflow,检查代码是否存在递归
  • 如果是OOM,检查是否有死循环创建线程或调用第三方接口创建线程,通过-Xss降低每个线程栈大小
③方法区溢出

方法区(又叫永久代,JDK8之后元空间替换了永久代),用于存放Class的相关信息,如:类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。

溢出原因:

1. 使用CGLib生成大量代理类2.Jdk7之前,频繁错误的使用String.intern方法3. 大量jsp和动态产生jsp4. 应用长时间运行,没有重启

排查思路:

  • 检查是否永久代空间设置的过小
    -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
  • 是否频繁错误使用String.intern
  • 是否与jsp有关
  • 是否使用CGLib生成大量代理类
  • 重启JVM
④直接内存溢出

直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是,这部分也被频繁的使用,也可能导致OOM。

原因:

1. 本机直接内存不受到Java堆大小限制,但是受到本机总内存大小限制2. 直接内存由-XX:MaxDirectMemorySize指定,如果不指定,默认与Java堆最大值一样(-Xmx3. NIO程序中,使用ByteBuffer.allocateDirect(capability)分配的是直接内存,可能导致直接内存溢出

排查思路:

  • 检查代码是否恰当
  • 检查JVM参数-Xmx(java堆最大值),-XX:MaxDirectMemorySize是否合理

参考:https://zhuanlan.zhihu.com/p/95150243