java内存泄漏检测工具?6步快速排查OOM

检测维修 0 11

程序出现OOM的问题, 每天都在崩溃。花了很多时间去排查却查不出原因, 这样做其实非常浪费精力。实际上要解决这个问题, 只需要看这六步就行了。

我工作这几年,遇到最烦的事就是程序突然OOM崩了。每次都要折腾好久,查日志、看监控,有时候还找不到原因。其实定位OOM没那么玄乎,有套固定流程,按步骤来几分钟就能锁定问题。

咱们首先得把OOM究竟是个什么东西给弄明白了, 简单来说呢, 它就是说JVM里面的某个内存区域已经塞满了, 然后系统就会甩出一个OutOfMemoryError这个错误给大家看。

大家千万别一上来就怀疑自己的代码里存在内存泄漏这个问题, 因为在很多实际情况里面, 很有可能是因为突然之间数据量变得特别大, 或者是当初给分配的内存空间本身就比较小所导致的。

这里大家要牢牢记住主要有这么三种可能出现的情况, 第一种情况是内存的相关参数设置得太小了, 第二种情况是内存里面创建对象的速度太快了, 导致垃圾回收器GC根本来不及进行处理和回收工作, 还有第三种情况就是有一些特定的对象长期一直占着内存空间而不愿意释放出来。

遇到OOM这一情况的第一步动作, 是先去看一看错误日志, 在日志里面会非常清楚地向你说明到底是哪一块区域被占满了, 最为常见的现象是显示有Java heap space这个标识, 这代表了堆内存已经溢出或者达到了极限, 同时通常会伴随着频繁发生的Full GC这种全量垃圾回收操作, 另外一种可能出现的情况是Metaspace被填满了, 也就是元空间区域出现了不足, 造成这一状况的根本原因一般是因为动态代理技术的使用或者是应用进行了热部署操作导致的, 再者还有可能是Direct buffer memory即直接缓冲内存出了问题, 如果在一个项目开发中大量使用了Netty这个网络通信框架那么很容易就会引发这样的内存问题。

“unable to create new native thread”这个情况, 表示线程的数目超出了限制。

“GC overhead limit exceeded”这个情况, 意味着垃圾回收的效率太低了, 简直就等于发出了严重泄漏的信号。

第二步, 要判断是不是被系统给终结了, Linux里面有一个叫做OOM Killer的机制, 假如进程是被这个机制处理掉了的话, 那么JVM的日志记录里面是一丁点的错误信息都没有的, 进程就会直接就消失了, 在这个时候运行`dmesg | grep -i kill`这样的命令或者是去查看`/var/log/messages`这个文件里面的内容, 都能够看到系统杀死进程的相应记录。

如果真的是系统将其杀死的情况, 那么操作者就需要考虑增加物理内存的配置方案, 或者是选择终止掉那些并非至关重要的大规模服务进程。

进行到第三步的时候, 你需要去确认一下是否存在着dump文件。这个步骤极其关键,因为如果你在事先已经将`-XX:+`这个参数配置好了的话。

使用`HeapDumpOnOutOfMemoryError`和`-XX:HeapDumpPath`参数。如果生成了`.hprof`文件, 则表明问题出现在堆或者元空间里面。

倘若没有生成dump文件, 并且在日志记录中发现了“Direct buffer memory”这个提示字样, 又或者看到了“unable to create new native thread”这样的报错内容, 那么就可以确定是由于直接内存问题, 或者是由于线程问题导致的, 因为属于这两种类别的OOM情况, 是绝对不会自动生成dump文件的。

完成第4步, 利用MAT对Dump文件开展分析工作。前往网上下载并安装免费版本的Memory Analyzer Tool软件, 待该软件启动完成之后, 应当首先去执行Leak Suspects Report这一项检查操作, 借此来让系统自动把那些最具有可疑嫌疑的软件对象给标注出来。

然后查看Dominator Tree, 按照保留堆的大小进行排序操作, 以此找到相关的业务类, 例如OrderCache和UserHolder, 同时不要过度关注byte类型的数据。

最后需要对可疑对象鼠标右键单击, 在弹出的菜单项中选择路径到达垃圾收集器根节点但不排除弱引用和软引用的选项, 由此能够观察强引用链的具体内容, 其结束位置通常是一个属于静态变量类别的集合元素、或者是被线程局部变量存储但未执行移除操作的状态, 也可能是监听器没有被注销的情况。

到了这个第五步的时候, 我们要开始根据分析出来的这些结果, 去修改代码了。大家需要关注那些常见的内存泄漏源头。

第一种是静态的HashMap, 它会不断地往里增加数据,却从来不减少;第二种是ThreadLocal变量,使用完之后不把它remove掉;第三种是回调函数在注册之后, 没有进行反注册的操作;第四种是连接池用完了, 却不释放资源。

如果仅仅是因为数据量变得非常庞大, 那么我们就应该把堆内存设置得更大一点, 或者是优化一下查询的功能, 加上分页的逻辑, 只返回必要的字段信息, 绝对不要把所有大文件的内容全部都塞进去。

第六步是做好日常的预防工作, 要控制好堆内存的大小, 让其不要超过8GB的额度, 因为如果超过了这个范围, 生成的dump文件就会变得非常大, 这会给后续的分析带来很大的困难, 在使用jstat -gc。

最后总结一下, 超快速定位的完整流程具体可以分为六个步骤, 第一步是看日志确定类型, 第二步是查系统以排除OOM Killer问题, 第三步是找dump来判断出问题的区域, 第四步是使用MAT来分析强引用链, 第五步是确定需要修改代码或者调整参数, 第六步则是进行日常监控以起到预防作用。

如果在平时不进行配置 dump 参数, 那么当事情发生的时候会连现场数据都抓不到, 即便是工具再强大也没有任何用处。最终解决问题的速度如何快慢, 关键就看前期的准备工作是否充分了。

相关推荐: