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社區,具體原因我也不清楚,不過好在問題解決了…