问题描述
突然有一天运维说 你这个内存不正常经常full gc 给了我个截图发现这个gc次数有点多啊,为什么gc次数会这么多呢?我就去查看应用的gc日志
发现full gc是 因为大对象 导致的fullgc ,然后我这个时候保护现场一下,jdump 将内存导出(慎用jdump 会导致卡死 我是重新构建一个新的应用 将流量切换到这个应用,然后流量将不能到打到老的应用)
问题分析
分析 hprof 文件发现一个对象 引用的内存大概有4M 我的天 怎么会这么大?而且为啥不会被释放呢?
来我们继续跟踪 点进一个对象查看详情 我草发现了这个对象是个大对象里面的有一个属性,竟然占用3M的内存空间,这个开发是真的讨打啊,写的什么鬼东西,再细看里面,这个configInfo是一个大json,里面放了一堆配置,果然没有最烂只有更烂,所以 数据库当中最好**别定义存储json的字段****,不然你后面维护都不知道这个json里面到底有啥,深深的给自己买了一个大坑,就算你自己很小心使用,其他对自己开发要求不一样的人,就可能会很随便,导致给团队造成了不可估量的损失。**
继续深究
我们最终发现了那个大对象是哪一个,但是问题的根本原因不是这个,大对象只是诱因。我发现这个是大对象是一个rpc请求返回的响应结果,所以应该是朝生夕死啊,按道理就算是大对象,上一次full gc应该就把这种对象的内存给释放了啊,为什么没有释放呢?神奇神奇神神奇,来我们继续分析从截图来看我们发现了一个重要信息
1.org.apache.dubbo.rpc.FutureContext
2.org.apache.dubbo.common.threadlocal.InternalThreadLocalMap
FutureContext 这个对象是 dubbo 用来处理异步请求时,获取异步请求结果的上下文的类
SerivceConsumer.sayHello("hello")
//可以拿到异步结果的对象
CompletableFuture<?> result=FutureContext.getContext().getCompletableFuture()
//我们可以对结果进行监听回调
result.whenComplete((v,e))->{}
可以看到dubbo的注释 这个类 用法类似 consumer异步调用 做链路跟踪时,需要对异步结果进行监听
那啥时候往这个FutureContext的ThreadLocal当中写入值的呢?
可以看到每次rpc都会往里面塞入值。org.apache.dubbo.rpc.protocol.AbstractInvoker#invoke
那什么时候回收呢?找找看
我草居然没找到
好吧原因找到了,原来是这个没有被回收,一直处于泄露状态,那dubbo为啥不回收呢?
1.在dubbo的角度 这个是给了一个thread local 变量 给到用户 可以拿到异步结果,那在什么时候回收呢?怎么回收呢 这个是需要考虑的?
dubbo 有个 ContextFilter 专门来清理 RpcContext, 如果dubbo 在ContextFilter当中调用了 清理FutureContext ,那可能有问题, 如果还有filter想拿到这个结果呢?
2.为啥dubbo不清理呢?
dubbo定位 应用之间的rpc 请求框架,期望的rpc数据报文是比较小的,所以不去清理了?而且这个都能计算的,是有限的空间。
如果本身是DubboServer,在作为Provider的时候又调用这个rpc ,我们分析源码可以知道线程池的大小,org.apache.dubbo.common.threadpool.support.fixed.FixedThreadPool 这个来构建线程池的,而且默认核心线程数为200个,而且如果不是作为Provider 而是tomcat直接访问rpc,那么就是计算tomcat的线程池的大小也差不多是200个,所以如果你的对象比较小,那么占用的总内存还是比较有限的。而且每次新请求都会覆盖上一个请求的响应。
那怎么解决这个问题呢?
1. 如果你的应用没有使用异步调用,而且也不需要使用FutureContext.getContext(),那么可以自定义实现Filter,来清理FutureContext。 2. 升级dubbo dubbo新版本提供了一些参数通过设置这个参数future.sync.set=false(dubbo最近的代码质量也比较差了,这个默认值应该为false,但是官方是true) future.clear.once=true 但是我认为这个没啥用 如果你前面设置了false 他都直接不调用,除非你真的是异步调用才有用,我最近要提个[Bug] future.sync.set default value error · Issue #14727 · apache/dubbo (github.com)把这个默认值给改了,这样就不用配置了。