Background
我们有个组件使用JAX-WS client来call另外一个组件的web service。新的release发布后,立马出现了cpu issue。
Cpu issue
截图如下(来自Cacti)
经调查发现,是因为我们的Stub对象创建过于频繁所致。每次API调用,都会新建一个Stub对象(创建代码如下)。而JAX-WS Stub对象的创建是cpu-sensitive的过程
static String SERVICE_URL = ...;
static String NAMESPACE_URI = ...;
public static MyService getMyService()
{
String endpoint = "http://" + SERVICE_URL + "/ws/MyService.wsdl";
URL url = new URL(endpoint);
QName qname = new QName(NAMESPACE_URI, "MyServiceService");
MyServiceService service = new MyServiceService (url, qname);
MyService port = service.getPort(MyService.class);
BindingProvider bp = (BindingProvider) port ;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);
return port;
}
很显然,我们必须重用stub对象,而不能每次重新创建。但问题是:
Is JAX-WS client thread-safe
如果你有google过这个问题,你一定很恼火Sun的JAX-WX RI的开发人员。
很多人希望有个官方回答,但只有非官方的线索。Stackoverflow,CXF,JBOSS
“根据JAX-WS的Spec, client proxy 是非线程安全”
经过考虑后,我们有几种方案:
- 创建一个全局的Stub。我们有程序员认为(未验证)Stub中全局共享的context变量在创建后并没有被修改,其他的都是每次调用生成的局部对象。所以单个Stub是线程安全的。
- 使用ThreadLocal,每个线程cache一个Stub对象
- 使用ObjectPool,类似于ThreadLocal,但不委托于Tomcat的Thread pool来管理对象的生命周期,而是自己来申请/释放
- 不使用JAX-WS,使用在其他项目中得到检验的Axis2的client包
- 从代码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
static String SERVICE_URL = ...;
static String NAMESPACE_URI = ...;
static MyServiceService service = null;
static
{
String endpoint = "http://" + SERVICE_URL + "/ws/MyService.wsdl";
URL url = new URL(endpoint);
QName qname = new QName(NAMESPACE_URI, "MyServiceService");
MyServiceService service = new MyServiceService (url, qname);
}
public static MyService getMyService()
{
MyService port = service.getPort(MyService.class);
BindingProvider bp = (BindingProvider) port ;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);
return port;
}
Still issue
产品上最终CPU/Memory没有问题。
但关于JAX-WS client stub(port)是否为线程安全,目前还没给出Final conclusion.
有人说,it's thread-safe