您的位置:首页 > 产品设计 > UI/UE

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

2011-11-15 18:14 501 查看

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的开发人员。

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

“根据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

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

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: