不正确使用Thrift Client导致的OOM问题排查

最近线上有一个多线程的任务,会调用几个Thrift服务。 上线后观察到这个脚本在执行一段时间后,会有好几次Full GC,然后就会报OOM错误。

那就先下载heap dump(推荐压缩后,使用rz下载到本地),使用VisualVM分析。首先切换到Objects页面,看下是否有大对象:

heapdump-Objects

可以看到,有两个byte数组占用了大量内存,也可以看到这个对象是在Java栈上的,接下来就是要找谁在使用这个变量。

右击该对象,点击Select in Threads:

可以看到是名为rebuilder-9的线程,再查看这个线程的调用栈:

再结合readStringBody的代码:

应该是这个Client一次读取太多数据导致的。

但是线上抓包后,发现没有Thrift服务会返回这么多数据。

然后只能去查调用的地方。最后发现TServerClient是被重用的。具体来说,对于用户定义的服务DemoService,Thrift会生成一个类 DemoService.Client 继承了TServiceClient。

调用者将这个TServiceClient在多个线程中使用,就会发生这样的情况:线程A读取了一半消息,这时线程B读取一部分,这样就导致了在特定情况下,解析出错,比如刚刚出现的情况,尝试读取非常大的字符串。

找到问题之后,剩下的就非常容易了:

从两方面入手,

  1. 使用jdk proxy机制,每次调用方法的时候,new一个新的Client来请求。这样就不会出现多线程读取导致的OOM了
  2. TBinaryProtocol在构造的时候,可以限定String长度,也为了防止其他类似的问题再导致OOM,我们在代码中加上了这个最大字符长度限制(10M)。以便在OOM之前就能发现问题。

幸运的是, 这个问题解决了之后,GC的问题也没有了。


之前没有用过VisualVM,对它的使用还不是很熟练,这方面确实得多练习练习。

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.