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

14 篇文章 0 订阅

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的过程 

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 是非线程安全”

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

  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调用重新生成。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FireCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值