一次线上OOM问题排查,使用Fastjson的坑

154 阅读2分钟

1. 起因

告警群收到相关内存溢出的告警通知

图片.png 此告警通知由全局异常捕获了,并主动发出了告警

2. 开始排查

此告警是在最上层的GlobalException抛出来的。查找此日志上下问,发现在调用服务端文件上传时(/inner/public/blob/form-data/upload),会抛出此错误。 这个接口是服务端内部进行文件上传的接口

2.1 复线此过程

在本地进行调用文件上传接口,发现每次上传大文件时20MB左右,都会抛出此错误。打断点进行查看。由于接口调用会通过aop来打印所有的出参和入参。发现文件能够正常上传到azure,走到自定义的aop时会抛出。定位到具体代码:

图片.png 此时将入参转化为String,使用com.alibaba.fastjson的JSON.toJSONString() 方法 图片.png

2.2 进入JSON.toJSONString() 方法

往里面走,内部通过将mp4转为base64,追加到字符串中。其中每次追加都会判断是否超过最大Size限制。如果达到了,就手动抛出OutOfMemoryError。

图片.png

当不设置为大对象时,最大字节为1073741824,为64Mb

图片.png

2.3 继续发现问题

由于发现是使用JSON.toJSONString() 方法,转json字符串时,mp4文件转成base64之后的大小超过了限定,从而往上抛。

奇怪的,在转json字符串,对其进行了try,catch。从断点来看,并没有进行到catch中。 为什么throw new OutMemoryError,在对应的方法调用方捕获不到。

尝试手动写一个throw new OutMemoryError,在方法调用方,能否捕获到。

图片.png 发现手动抛出OutMemoryError时,JVM层面直接终止程序

2.3.1 使用springboot启动

但是发现,实际上服务并没有被杀死,还能正常访问

通过debug往下走,发现当手动抛出OutMemoryError时,spring的AopUtils,帮我们捕获对应的TargetException目标异常。不会抛到调用的方法。

图片.png

2.3.2 异常被全局GolbalException捕获

通过GolbalException,兜底异常进行了捕获

图片.png

这个过程是由于JSON.toJSONString() 方式时,由于上文的是文件并对其进行解析json,导致超出的其上线,手动抛出OOM,在上层被兜底异常进行了捕获

3. 总结

3.1 JProfiler工具的使用

在此时排查的过程中,使用了JProfiler工具,打印dump分析堆内存。内存的大小、对象的占用情况。

未修复之前 内存进行放大了5倍

图片.png

修复之后 对JSON.toJSONString()进行了删除处理,内存指标正常,上报20M文件,内存上升20M

图片.png

3.2 dump分析

-XX:+HeapDumpOnOutOfMemoryError

-XX:-OmitStackTraceInFastThrow

-XX:HeapDumpPath=/home