当前位置: 首页 > article >正文

JVM系列之OOM实战

      工作以来,陆陆续续遇见Java应用OOM情况非常多了。每次未定位到原因时总是让人战战兢兢,特别是发生在交易频繁的系统身上,压力更大。因此,编写代码的时候,不时问问自己,我们这样写,会不会引发OOM。分享一篇关于OOM的经历,希望能给大家带来一点解决问题的灵感吧。

      导致OOM的原因很多,有堆内存溢出,有元空间溢出,有栈溢出。但是堆内存溢出最为常见,其中堆内存溢出中以大对象或者常驻内存无法被回收(内存泄漏)导致最终内存溢出最为常见。当然,所谓的大对象也是相对虚拟机分配的内存来说的。但在编码的过程中,我们尽量避免大对象的产生。

       大概在两年前的一个周五晚上,正好是交易相对繁忙的时候,笔者亲身经历一个生产OOM故障,至今印象十分深刻。因为交易超时触发告警,去机房一看日志和机器情况,发现已经一台主机的微服务应用A已经OOM了,然后约1小时之后,另一个主机的微服务应用A也OOM,通过翻看日志,发现溢出和业务逻辑代码没有特别明显的关系。第二天上午,备机房的两个应用A也相继触发了OOM,下图是测试环境模拟出来的OOM日志,可以明显看到java.lang.OutOfMemoryError:Java heap space的溢出信息。

      这是使用jvisualvm观察应用在测试环境慢慢溢出的内存使用情况,可以看出内存可以回收的越来越少,逐渐上涨,这是非常不健康的回收情况,如下图:

      通过jstat工具来查看应用的回收情况,也能看出溢出的情况,如下图:

      通过top命令可以看到第进程CPU飙升,通常内存溢出时,都会表现CPU飙升,其实这时CPU都在忙着回收基本无法回收的垃圾,溢出啦。如下图:

通过用mat分析Dump错误信息如下:

       根据日志报有OutOfMemory类型错误,分析有存在内存泄露,dump文件的错误类型有dubbo应用包占比过多,还有finalize优先级低于GC的线程处理占用过多内存。仔细检查项目代码,未发现应用程序层面的问题。随后安排进行了较长时间的稳定性测试。生产极可能为多天积累导致的内存泄露。

     测试时为了较快复现问题,在服务端设置线程sleep三秒钟,并加大并发量,缩小虚拟机内存。测试工具为loadrunner,监控工具为jvisualvm、jconsole。

测试结果如下:

两个多小时的持续压测,系统老年代的堆栈一直居高,不能正常回收。

jconsole观测如下图:

     运行12个多小时后,内存耗尽,堆内存不能正常回收,检查dump等文件,测试环境的日志报错和生产也完全一致,算是把问题重现了。使用jvisualvm观察如下图:

     老年代的内存不能释放被回收,后仅对应用A进行性能测试,不通过dubbo连接微服务,一切正常,老年代堆栈可以正常回收。判断极可能为dubbo相关包的问题。同时,通过mat对dump日志分析,也指向dubbo相关jar包,后搜索相关类,和排查dubbo相关包源码,发现spring引入的dubbo关联包为快照版,包名为spring-xxx-dubbo-xxx-SNAPSHOT.jar,该包为非正式版,通过排除相关类,发现有做了一些统计操作和通讯加上了同步锁(synchronized),资源释放慢,很容易导致老年代内存回收失败。

替换为产品之前确认的dubbo-spring-xxxx.jar正式包,同样是加大并发,缩小内存来压测,测试情况如下:

运行3个小时后,堆栈内存正常回收一次

运行7个小时后,堆栈内存正常回收

       运行20个小时后,堆栈运行依然正常稳定,看到规律的回收情况,心里的石头算是落地了。

       后记,在实际的生产中,内存溢出问题的原因很多,但是大都因为使用对象不当。例如查库一次性获取数据量大导致大对象产生,这时溢出异常日志往往上面是有明显的业务逻辑代码的操作。这类OOM比较好处理,通过优化业务逻辑代码即可。像本次非逻辑代码相关的,还需要借助mat来分析dump,并逐步缩小范围排除来找出问题根因。


 

                         


http://www.kler.cn/a/414935.html

相关文章:

  • 利用zabbix自定义脚本监控MySQL基础状态
  • 【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
  • [保姆式教程]使用labelimg2软件标注定向目标检测数据和格式转换
  • Git上传本地项目到远程仓库(gitee/github)
  • Flink高可用配置(HA)
  • C中指针在64位操作系统下为什么是4而不是8
  • 【论文阅读】Federated learning backdoor attack detection with persistence diagram
  • idea新建springboot web项目
  • YOLOv8-ultralytics-8.2.103部分代码阅读笔记-autobatch.py
  • 【UE5 C++课程系列笔记】05——组件和碰撞
  • Ubuntu nvidia-cuda-toolkit 升级
  • Chrome://常用的内部页面地址
  • java回文数
  • MySQL 启动失败问题分析与解决方案:`mysqld.service failed to run ‘start-pre‘ task`
  • 在 Ubuntu 18.04 上安装 MySQL 5.7和MySQL 8
  • 【网络安全 | 漏洞挖掘】绕过SAML认证获得管理员面板访问权限
  • Python知识分享第九天补充
  • rocylinux9.4安装prometheus监控
  • js:循环、数组
  • 网络技术-VRRP(虚拟路由冗余协议)部署介绍
  • Element UI Collapse 折叠面板和表格结合高度闪动问题
  • daos源码编译
  • Redis 可观测最佳实践
  • 变换矩阵左乘与右乘的区别和应用
  • Linux开发者的CI/CD(11)jenkins变量
  • 代码随想录算法训练营第六十天|Day60 图论