产品issue 诊断:Java CPU/Memory- Caused by JAX-WS Client

转自 http://blog.csdn.net/firecoder/article/details/6973546

Background

我们有个组件使用JAX-WS client来call另外一个组件的web service。新的release发布后,立马出现了cpu issue。

Cpu issue

截图如下(来自Cacti)

cpu goes to 100%

 

经调查发现,是因为我们的Stub对象创建过于频繁所致。每次API调用,都会新建一个Stub对象(创建代码如下)。而JAX-WS Stub对象的创建是cpu-sensitive的过程 

[java] view plaincopy
 
  1. static String SERVICE_URL = ...;  
  2. static String NAMESPACE_URI = ...;  
  3.   
  4. public static MyService getMyService()  
  5. {   
  6.   String endpoint = "http://" + SERVICE_URL + "/ws/MyService.wsdl";  
  7.   URL url = new URL(endpoint);  
  8.   QName qname = new QName(NAMESPACE_URI, "MyServiceService");  
  9.   
  10.   MyServiceService service = new MyServiceService (url, qname);  
  11.   MyService port = service.getPort(MyService.class);   
  12.   BindingProvider bp = (BindingProvider) port ;  
  13.   bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);  
  14.    
  15.   return port;  
  16. }  

很显然,我们必须重用stub对象,而不能每次重新创建。但问题是:

Is JAX-WS client thread-safe

如果你有google过这个问题,你一定很恼火Sun的JAX-WX RI的开发人员。

很多人希望有个官方回答,但只有非官方的线索。StackoverflowCXFJBOSS

“根据JAX-WS的Spec, client proxy 是非线程安全”

经过考虑后,我们有几种方案:

 

  1. 创建一个全局的Stub。我们有程序员认为(未验证)Stub中全局共享的context变量在创建后并没有被修改,其他的都是每次调用生成的局部对象。所以单个Stub是线程安全的。
  2. 使用ThreadLocal,每个线程cache一个Stub对象
  3. 使用ObjectPool,类似于ThreadLocal,但不委托于Tomcat的Thread pool来管理对象的生命周期,而是自己来申请/释放
  4. 不使用JAX-WS,使用在其他项目中得到检验的Axis2的client包
  5. 从代码change的范围来讲,1~4排列,从小到大。1是将自己放在推测上,舍弃;于是用2

 

修改上线后,CPU下降显明,我们过了几天好日子。

 

左图为每次重新创建Stub,右图为重用Stub对象。(来自JConsole)

Memory Issue

截图如下(来自jvisualvm),使用的内存缓慢上升;运行几天后,内存被耗尽(2G),开始频繁的GC,导致CPU再次相当繁忙。

 

通过对Heap-Dump(来自MAT)的分析,我们发现占用Heap的对象为线程对象。

 

java.lang.Thread @ 0x75426748 http-8930-1 - 4,353,584 (1.02%)bytes.

 

Thread中retained对象是ThreadLocal,而ThreadLocal中cache了Stub对象, 每个Stub对象为1M左右。由于Tomcat默认线程池大小是200个,

我们的应用中每个线程会cache三个Stub对象,所以理论上Stub占用的内存为600M。

注意在我们的Tomcat中部署了多个应用,这些应用共享Tomcat的线程池,所以线程空闲的机会很少,所以线程被销毁的机会也就恨小。

在产品环境,我们发现内存占用可达到1.8G,与理论里不符。

在检查了该组件的内存使用历史记录后,我们发现这个组件在使用JAX-WS client之前内存使用也一直很高。

JAX-WS Client成了压垮它的最后一根稻草。

 

关于ThreadLocal + ThreadPool的组合, 网上有文说it's a bad idea,至少要慎用

Second Fix

在Memory issue出现后,我们又重新从头到审查这个issue。
我们发现在stub的创建过程,耗时操作在Service的创建上,而Port(Stub)的创建相对要轻量很多,于是我们作了如下的Fix:
全局共享一个Service对象,Port对象每次API调用重新生成。

 

[java] view plaincopy
 
  1. static String SERVICE_URL = ...;  
  2. static String NAMESPACE_URI = ...;  
  3.   
  4. static MyServiceService service = null;  
  5.   
  6. static   
  7. {  
  8.   
  9.  String endpoint = "http://" + SERVICE_URL + "/ws/MyService.wsdl";  
  10.   URL url = new URL(endpoint);  
  11.   QName qname = new QName(NAMESPACE_URI, "MyServiceService");  
  12.   
  13.   MyServiceService service = new MyServiceService (url, qname);  
  14. }  
  15.   
  16. public static MyService getMyService()  
  17. {   
  18.    
  19.   MyService port = service.getPort(MyService.class);   
  20.   BindingProvider bp = (BindingProvider) port ;  
  21.   bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);  
  22.    
  23.   return port;  
  24. }  

Still issue

产品上最终CPU/Memory没有问题。

但关于JAX-WS client stub(port)是否为线程安全,目前还没给出Final conclusion.

有人说,it's thread-safe

posted @ 2014-10-30 00:23  princessd8251  阅读(243)  评论(0)    收藏  举报