不正确使用Thrift Client导致的OOM问题排查
最近线上有一个多线程的任务,会调用几个Thrift服务。 上线后观察到这个脚本在执行一段时间后,会有好几次Full GC,然后就会报OOM错误。
那就先下载heap dump(推荐压缩后,使用rz下载到本地),使用VisualVM分析。首先切换到Objects页面,看下是否有大对象:
可以看到,有两个byte数组占用了大量内存,也可以看到这个对象是在Java栈上的,接下来就是要找谁在使用这个变量。
右击该对象,点击Select in Threads:
可以看到是名为rebuilder-9的线程,再查看这个线程的调用栈:
再结合readStringBody的代码:
应该是这个Client一次读取太多数据导致的。
但是线上抓包后,发现没有Thrift服务会返回这么多数据。
然后只能去查调用的地方。最后发现TServerClient是被重用的。具体来说,对于用户定义的服务DemoService,Thrift会生成一个类 DemoService.Client
继承了TServiceClient。
调用者将这个TServiceClient在多个线程中使用,就会发生这样的情况:线程A读取了一半消息,这时线程B读取一部分,这样就导致了在特定情况下,解析出错,比如刚刚出现的情况,尝试读取非常大的字符串。
找到问题之后,剩下的就非常容易了:
从两方面入手,
- 使用jdk proxy机制,每次调用方法的时候,new一个新的Client来请求。这样就不会出现多线程读取导致的OOM了
- TBinaryProtocol在构造的时候,可以限定String长度,也为了防止其他类似的问题再导致OOM,我们在代码中加上了这个最大字符长度限制(10M)。以便在OOM之前就能发现问题。
幸运的是, 这个问题解决了之后,GC的问题也没有了。
之前没有用过VisualVM,对它的使用还不是很熟练,这方面确实得多练习练习。
不正确使用Thrift Client导致的OOM问题排查