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 无用!

分析服务器堆栈:

dubbo kryo 入参泛型 dubbo泛化调用内存泄露_缓存


通过堆栈dump,其分析结果与异常信息一致,为sendThread出出现内存溢出,通过下图可见,主要是org.apache.zookeeper.ZooKeeper$ZKWatchManager相关得实例大量再堆里面创建并最终导致内存溢出

dubbo kryo 入参泛型 dubbo泛化调用内存泄露_泛化_02

dubbo kryo 入参泛型 dubbo泛化调用内存泄露_泛化_03


dubbo kryo 入参泛型 dubbo泛化调用内存泄露_java_04


到此,可以断定是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 kryo 入参泛型 dubbo泛化调用内存泄露_dubbo kryo 入参泛型_05

这次遇到得问题应该时dubbo之前版本遗留得一个bug,程序中泛化调用时会用到比较多的接口,导致正常情况下ReferenceConfig实例数量本身就多,可能触发了dubbo隐藏得某些bug,目前具体原因还未知,不过在新版本dubbo中已经将setGeneric和setApplication方法废弃掉了,由于我一直没有十分关注dubbo社区,具体原因我也不清楚,不过好在问题解决了…

dubbo kryo 入参泛型 dubbo泛化调用内存泄露_缓存_06


dubbo kryo 入参泛型 dubbo泛化调用内存泄露_dubbo kryo 入参泛型_07