dubbo客户端泛化调用服务端接口时,客户端程序分配2G内存,但是服务最多运行1小时就会内存溢出,
异常信息如下(异常显示SendThread这个线程出出现了内存溢出):
[rpc_dubbo_call_thread_1_1-SendThread(127.0.0.1:2182)] WARN org.apache.zookeeper.server.ZooKeeperThread - Exception occurred from thread rpc_dubbo_call_thread_1_1-SendThread(127.0.0.1:2182)
java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.newNode(HashMap.java:1750)
at java.util.HashMap.putVal(HashMap.java:631)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
at java.util.AbstractCollection.addAll(AbstractCollection.java:344)
at org.apache.zookeeper.ZooKeeper$ZKWatchManager.materialize(ZooKeeper.java:177)
at org.apache.zookeeper.ClientCnxn$EventThread.queueEvent(ClientCnxn.java:477)
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1176)
为便于快速重现该异常,我将程序得运行内存改为512M,几分钟之后老年代迅速占用100%,且FULL GC 无用!
分析服务器堆栈:
通过堆栈dump,其分析结果与异常信息一致,为sendThread出出现内存溢出,通过下图可见,主要是org.apache.zookeeper.ZooKeeper$ZKWatchManager相关得实例大量再堆里面创建并最终导致内存溢出
到此,可以断定是dubbo异常,由于程序只有一个地方用到了dubbo,且使用得是dubbo得泛化调用模式,由此我基本可以断定问题所在即在此处:
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setApplication(application);
reference.setRegistry(registry);
reference.setInterface(interfaceClass); // 接口名
reference.setGeneric(true); // 声明为泛化接口
//ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接,
//需要缓存,否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏。
//API方式编程时,容易忽略此问题。
//这里使用dubbo内置的简单缓存工具类进行缓存
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
GenericService genericService = cache.get(reference);
if(genericService == null) {
cache.destroy(reference);
throw new IllegalStateException("服务不可用");
}
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
return genericService.$invoke(methodName, new String[] {"java.util.Map"}, parameters);
}
就如上面代码中得注释所指,由于ReferenceConfig比较重,因此必须要缓存起来,由于程序在泛化调用时,存在多个不同接口,因此肯定会有多个ReferenceConfig生成,如果不缓存得话,随着调用得次数增多,肯定会导致
ReferenceConfig实例越来越多,占用许多系统资源,但是我已经缓存了的,为什么还会出现这个问题呢,我搜了一下dubbo下关于内存溢出得issue,基本上看了一遍,发现基本上都怀疑是ReferenceConfig出了问题,
于是,我将ReferenceConfig变成单例,再次启动程序,发现程序得内存使用率大为改善,此时更加断定就是泛化调用多个不同接口导致ReferenceConfig出现问题
查看dubbo源码中consumer得demo,发现在泛化调用时引入了一个新的对象DubboBootstrap,按照最新示例将原有泛化调用方式改正之后如下:
public Object genericInvoke(String interfaceClass, String methodName, Object[] parameters){
System.out.println("当前调用得接口名和方法为:"+interfaceClass+";"+methodName);
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
reference.setInterface(interfaceClass); // 接口名
reference.setGeneric("true"); // 声明为泛化接口
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap.application(application)
.registry(registry)
.reference(reference)
.start();
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
GenericService genericService = cache.get(reference);
if(genericService == null) {
cache.destroy(reference);
throw new IllegalStateException("服务不可用");
}
return genericService.$invoke(methodName, new String[] {"java.util.Map"}, parameters);
}
分析程序运行状况发现,堆内存得占用恢复正常;
这次遇到得问题应该时dubbo之前版本遗留得一个bug,程序中泛化调用时会用到比较多的接口,导致正常情况下ReferenceConfig实例数量本身就多,可能触发了dubbo隐藏得某些bug,目前具体原因还未知,不过在新版本dubbo中已经将setGeneric和setApplication方法废弃掉了,由于我一直没有十分关注dubbo社区,具体原因我也不清楚,不过好在问题解决了…