公司埋点服务端跑一段事件就会挂掉,导致服务不可用,稳定性差。下面是排查过程。

特征分析

这个问题有三个特征。
一是运行一段时间后才会宕机:不同于一些编译问题与业务异常可以立马失败,可以很方便的复现。这个问题通常是跑一段时间后才会暴露出来,并且具体跑多久还不是统一的。所以解决这个问题,现场日志和快照非常重要。我也是通过这个特征推断服务是因为内存泄漏导致宕机的。

二是你可以在服务过程中感受到服务越来越卡慢:由于内存资源的不足导致对内存资源的竞争更加激烈,从而导致服务卡慢。同时内存不足会导致GC线程与用户线程竞争cpu。

三是jvm GC会很频繁,会报一个gc overhead limit exceeded的错误消息:由于内存不足,频繁触发full gc导致gc时间超过了用户代码执行时间。系统会报这样一个错误。

.hprof现场文件分析与业务日志分析

最后的业务日志有大量的mybatis的Too Many Result错误信息,同时有gc overhead limit exceeded错误信息。此时同事推测是由于大量的Exception对象导致的内存溢出。
然后我下载回hprof堆快照文件,导入分析工具进行分析:

内存分析

在大对象里面可以看到,ArrayList占用88%的内存,同时下面有提示这个歌可能存在内存泄漏问题。我们接着查看对象堆栈跟踪(Stack Trace)和引用树(Reference Tree):

堆栈跟踪

堆栈跟踪

上面堆栈跟踪可以看到相关联可能的错误方法,其中Pagehelper是之前杰哥曾遇到过他的内存泄漏问题曾分享给我,而Skywalking内存泄漏是之前我在调研链路追踪的时候了解到它有内存泄漏问题。而selectOne则是报错方法。

引用树

我们可以看到引用树中这个mysql.jdbc的ByteArrayRow.internalRowData的内存占用太高了。

注:.hprof这个文件对我们分析jvm问题很有帮助,如果你的服务没有在错误的时候自动导出需要添加参数

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=pathto/filename.hprof

可能是skywalking、pagehelper与代码问题。

首先我个人一直倾向于,绝大多数问题都是由于程序员自己导致的。程序员自己代码写的不够完善导致了产生了这样的或者那样的bug。

我排除了pagehelper由于ThreadLocal未清除条件导致的内存泄漏,因为我们这个项目实际上没有使用到Pagehelper,没做分页。然后排除低版本Skywalking内存泄漏的bug。

最后问题集中在这个报To Many Result的selectOne方法上面:

User user = new User();
user.setUserId(userId);
return this.selectOne(user);

在userId为null的时候。user的所有字段都为null。而这种情况下selectOne的行为是不带where条件直接查询,SQL类似:

select * from user;

于是查出来百万条数据,于是抛了个Too Many Result的异常。而我们的mq在异常后会进行重试,重试又异常。如此往复便打死了服务。

总结:

三板斧:特征定位问题可能类型, 日志、快照文件分析可能原因,根据经验排查原因。

标签: debug, 内存泄漏

仅有一条评论

  1. 小涛仔

    李哥, 图挂了~

添加新评论