使用阿里arthas 定位oom问题原因

17,340 阅读5分钟

1,最近我们生产服务器在做营销活动的时候突然发生oom,堆内存溢出。这个很让人头疼, 我们开发排查了好长时间,最终找到了问题的所在。这里我先买一个关子,问题的原因大家看完文章就一清二白了。

2,首先我们自己创建一个测试的项目 到spring.io 上面创建一个springboot 的项目组件选择web.这里多说一下阿里最近也出了自己的自己项目构建库,地址https://start.aliyun.com/ 具体使用和spring.io 差不多,有兴趣的同学解压自己看一下。 先写一个template 的配置类,生命为一个bean

在写一个测试的contellor,
这里代码很简单,就是发送一个get请求,传入number 参数,就能指定创建多少个http的连接。 这里我们先用postman 测试一下 结果如下

,这里由于是模拟,我们把堆内存调小一些,发生oom生成dump文件,打印gc详情等。 参数 -- -Xloggc:d:\gc.log 生成gc 的日志 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:\gc.dump 发生oom 生成dump的文件名和目录 -Xms20M //最大堆内存 -Xmx20M //最小堆内存 -XX:+PrintGC 输出GC日志 -XX:+PrintGCDetails 输出GC的详细日志 -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)) -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息 将这些东西配置好以后我们在运行一下,发送number=1000,看一下控制台的输出

我们看见报错了,并且还生成了dump 文件。 这说明代码可行的。 我们将其打成jar包,上传到服务器,修改一下gclog 和dump的日志路径 。 并运行
运行后的界面为
我们先不发送get请求,我们先下载arthas,这是阿里开源的一个java 性能诊断工具非常好用 官方介绍 Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态? 怎么快速定位应用的热点,生成火焰图? Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。 github 地址https://github.com/alibaba/arthas 官方地址 alibaba.github.io/arthas/inst… 我这边下载的是arthas-boot.jar ,直接java -jar 就可以运行 我们先将其跑起来
这里会让你选择哪个java服务,默认他会检测本机所有的java服务。前面的29412 就是进程号,和你用jps 查看的一样。
在arthas 控制台里面输入dashboard 看到系统详情
这里看一下内存情况,总体使用了60%, 基本上内存都分给老年代了,年轻代的gc 次数远大于老年代的gc. 但是耗时是老年代的gc 大于年轻代的。 我们用postman
来模拟大量的请求。 控制台发生了
这个时候再看arthas 的界面
发现是老年代频繁的发生fullgc,也就是说有大对象直接生成,不经过伊甸园直接到达老年代。 再看一下之前的gc.log 的日志内容
验证了我们的猜想,频繁的进行fullgc,并且老年代就没有有效的回收

回收的空间为零。我们要做的就是找到这个大对象。 我们在arthas的控制台上使用classloader 的命令 结果为
我们在根据刚才报错提示执行sc -d org.apache.coyote.http11.Http11OutputBuffer 结果如图
表明这个类的类加载器是同一个,大对象就是它。 我们在到Tomcat 的核心jar包里面看一下 根据提示找到这个方法
点击进去看一下
是headbuffer过大造成的。 这里我们在看一下配置
不知道是谁把请求头的大小设置了1兆。 这里我们改一下改为10k 在测试一下看内存的使用情况
最终结果如下
我们发现fullgc 的次数明显的少了下来。调优就是减少fullgc的。 住在这里arthas能让我们快速的定位问题的原因,根据监控仪表盘找到发生oom 的原因, 在根据classloader 找到 产生最多的类,找到类加载器,在感觉sc -d class 确认是否是这个类造成的。最后就解决问题了。 要是小伙伴感兴趣可以自己试一下。 3,下面我们在说一下arthas 的其他用法 其中我最喜欢的是 redefine 这个命令, 这个命令可以动态的替换class的文件, redefine Load the external *.class files to re-define the loaded classes in JVM. Reference: Instrumentation#redefineClasses Notes: Re-defined classes cannot be restored. There are chances that redefining may fail due to some reasons, for example: there’s new field introduced in the new version of the class, pls. refer to JDK’s documentation for the limitations. The reset command is not valid for classes that have been processed by redefine. If you want to reset, you need redefine the original bytecode. The redefine command will conflict with the jad/watch/trace/monitor/tt commands. After executing redefine, if you execute the above mentioned command, the bytecode of the class will be reset. The reason is that in the JDK redefine and retransform are different mechanisms. When two mechanisms are both used to update the bytecode, only the last modified will take effect. 这是官方的解释。我们来具体使用一下。
我们把返回的sucess 的改为6666 ,再重新编译生成class 文件,上传到服务器的上面。
执行 redefine /tmp/SpringbootArthasApplication.class 我们看到修改返回成功了
这个功能还是很好用的,在紧急的情况下,可以修改生产的数据。 更多的arthas 的功能,大家还是自己发掘吧。