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

本文详细阐述了如何解决使用JAX-WS客户端调用Web服务时遇到的线程安全问题及内存泄露问题,通过全局共享服务对象并按需创建代理对象,最终成功优化了性能并解决了内存占用过大的问题。

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

解析错误:/opt/module/jdk1.8.0_212/bin/java -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath /root/tms-realtime/0ynRPqCQZk:/root/tms-realtime/PnJbgdYeEi/flink-java-1.16.1.jar:/root/tms-realtime/oT5HugdKa0/flink-core-1.16.1.jar:/root/tms-realtime/lRaNr4TMMY/flink-annotations-1.16.1.jar:/root/tms-realtime/wrrijKymgt/flink-metrics-core-1.16.1.jar:/root/tms-realtime/HrviJCijB9/flink-shaded-asm-9-9.2-15.0.jar:/root/tms-realtime/tuqiaX9thY/flink-shaded-jackson-2.12.4-15.0.jar:/root/tms-realtime/nq7iasFvvP/kryo-2.24.0.jar:/root/tms-realtime/EUcrkkdDqF/minlog-1.2.jar:/root/tms-realtime/rDrrRBcnsL/objenesis-2.1.jar:/root/tms-realtime/n6hwqpuCGP/commons-collections-3.2.2.jar:/root/tms-realtime/Xktf2Nt2p6/commons-compress-1.21.jar:/root/tms-realtime/u3W4SXySQv/commons-lang3-3.3.2.jar:/root/tms-realtime/aV0xwZW5CA/commons-math3-3.6.1.jar:/root/tms-realtime/8lp2OSaKN1/chill-java-0.7.6.jar:/root/tms-realtime/6283HVd4Oi/jsr305-1.3.9.jar:/root/tms-realtime/IHW5nhjjFB/flink-shaded-force-shading-15.0.jar:/root/tms-realtime/3rDENfoGcQ/flink-streaming-java-1.16.1.jar:/root/tms-realtime/n6W3clLfTw/flink-file-sink-common-1.16.1.jar:/root/tms-realtime/m4A7gbsc2U/flink-runtime-1.16.1.jar:/root/tms-realtime/YDynkAfR54/flink-rpc-core-1.16.1.jar:/root/tms-realtime/S41SsAe1mI/flink-rpc-akka-loader-1.16.1.jar:/root/tms-realtime/kMpDvGx1pI/flink-queryable-state-client-java-1.16.1.jar:/root/tms-realtime/IEfdXE4xjf/flink-hadoop-fs-1.16.1.jar:/root/tms-realtime/xCpwaLL9LC/commons-io-2.11.0.jar:/root/tms-realtime/i6H7GS1IXE/flink-shaded-netty-4.1.70.Final-15.0.jar:/root/tms-realtime/iUyw43FTVc/flink-shaded-zookeeper-3-3.5.9-15.0.jar:/root/tms-realtime/9CH0CqOEmQ/javassist-3.24.0-GA.jar:/root/tms-realtime/4ydP87Lxg8/snappy-java-1.1.8.3.jar:/root/tms-realtime/ZUtZ3odN0H/lz4-java-1.8.0.jar:/root/tms-realtime/4cpTuRRNV6/flink-shaded-guava-30.1.1-jre-15.0.jar:/root/tms-realtime/wKNiueSrvD/flink-connector-kafka-1.16.1.jar:/root/tms-realtime/tG2LslzjdA/flink-connector-base-1.16.1.jar:/root/tms-realtime/K69s0tlouL/kafka-clients-3.2.3.jar:/root/tms-realtime/GHyMUMYsxP/zstd-jni-1.5.2-1.jar:/root/tms-realtime/uuBm0qVDt3/fastjson-1.2.68.jar:/root/tms-realtime/PPx7fVbxJl/guava-27.0-jre.jar:/root/tms-realtime/rCXuigExt6/failureaccess-1.0.jar:/root/tms-realtime/81eLFMyTgL/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:/root/tms-realtime/59OipY0VHI/checker-qual-2.5.2.jar:/root/tms-realtime/QujS4lb6TD/j2objc-annotations-1.1.jar:/root/tms-realtime/tZ2ENLM46a/animal-sniffer-annotations-1.17.jar:/root/tms-realtime/4lfs5Miq2d/jakarta.activation-api-1.2.1.jar:/root/tms-realtime/5XoBvr0hGT/jetty-servlet-9.4.43.v20210629.jar:/root/tms-realtime/kkgfX1CpBU/jetty-security-9.4.43.v20210629.jar:/root/tms-realtime/Wt0nCwYfY0/jetty-util-ajax-9.4.43.v20210629.jar:/root/tms-realtime/otmYZb9y2f/jackson-databind-2.12.7.jar:/root/tms-realtime/u9gyV258pB/jackson-core-2.12.7.jar:/root/tms-realtime/UjvFI3bOkY/jackson-annotations-2.12.7.jar:/root/tms-realtime/KUzIWsW765/jaxb-api-2.2.11.jar:/root/tms-realtime/iAAi8XivnI/jetty-client-9.4.43.v20210629.jar:/root/tms-realtime/vgtoSgw1tB/jetty-http-9.4.43.v20210629.jar:/root/tms-realtime/XskJxDj1fh/jetty-util-9.4.43.v20210629.jar:/root/tms-realtime/MbycpyN9Ql/jetty-io-9.4.43.v20210629.jar:/root/tms-realtime/sQ9Ao2EPio/javax.servlet-api-3.1.0.jar:/root/tms-realtime/1IoAGxSRfn/jackson-module-jaxb-annotations-2.12.7.jar:/root/tms-realtime/0X6rAiRqxd/jakarta.xml.bind-api-2.3.2.jar:/root/tms-realtime/gb2WHy0cTD/jackson-jaxrs-json-provider-2.12.7.jar:/root/tms-realtime/v3MsomWjwT/jackson-jaxrs-base-2.12.7.jar:/root/tms-realtime/XpwvYqZN64/flink-clients-1.16.1.jar:/root/tms-realtime/L895zM5FzH/flink-optimizer-1.16.1.jar:/root/tms-realtime/THEGJP69NZ/commons-cli-1.5.0.jar:/root/tms-realtime/pj1Ta59QUy/flink-connector-jdbc-1.16.1.jar:/root/tms-realtime/iLOegq5zkp/flink-connector-mysql-cdc-2.3.0.jar:/root/tms-realtime/AZwWiH3OqL/flink-connector-debezium-2.3.0.jar:/root/tms-realtime/6dFbb44sWO/debezium-api-1.6.4.Final.jar:/root/tms-realtime/lcdlFNSnxA/debezium-embedded-1.6.4.Final.jar:/root/tms-realtime/rai7SgRhWA/connect-api-2.7.1.jar:/root/tms-realtime/yMUKxdyg84/javax.ws.rs-api-2.1.1.jar:/root/tms-realtime/J9ic1oFgZr/connect-runtime-2.7.1.jar:/root/tms-realtime/uDy64QFg74/kafka-tools-2.7.1.jar:/root/tms-realtime/e4F9z6mUCD/argparse4j-0.7.0.jar:/root/tms-realtime/a2Ee4tQAJT/connect-transforms-2.7.1.jar:/root/tms-realtime/GJj2U4q4Yu/jersey-container-servlet-2.31.jar:/root/tms-realtime/U4FjIADyaQ/jersey-container-servlet-core-2.31.jar:/root/tms-realtime/zyru5sDEUq/jakarta.inject-2.6.1.jar:/root/tms-realtime/YBQozVIzBz/jakarta.ws.rs-api-2.1.6.jar:/root/tms-realtime/mnzQ9oSK1h/jersey-hk2-2.31.jar:/root/tms-realtime/muq1O1f8Wd/hk2-locator-2.6.1.jar:/root/tms-realtime/vI2POmzkMH/aopalliance-repackaged-2.6.1.jar:/root/tms-realtime/V4jVIIkXcO/hk2-api-2.6.1.jar:/root/tms-realtime/XoPGv1ApAP/hk2-utils-2.6.1.jar:/root/tms-realtime/XhNR3rOeTv/activation-1.1.1.jar:/root/tms-realtime/fc4VD7gvie/jetty-server-9.4.38.v20210224.jar:/root/tms-realtime/iqZQOl6Xp1/jetty-servlets-9.4.38.v20210224.jar:/root/tms-realtime/zEfF6JdlPb/jetty-continuation-9.4.38.v20210224.jar:/root/tms-realtime/xHiIycruRM/reflections-0.9.12.jar:/root/tms-realtime/KVuIFOF0Vs/maven-artifact-3.6.3.jar:/root/tms-realtime/8dUnFRHUoT/plexus-utils-3.2.1.jar:/root/tms-realtime/BHufjSQtyC/connect-json-2.7.1.jar:/root/tms-realtime/Jr0O5PzkLg/jackson-datatype-jdk8-2.10.5.jar:/root/tms-realtime/A1scIil9Zq/connect-file-2.7.1.jar:/root/tms-realtime/8d59ipSBWF/debezium-connector-mysql-1.6.4.Final.jar:/root/tms-realtime/8NmphBvBe0/debezium-core-1.6.4.Final.jar:/root/tms-realtime/vLiw7ThjCM/jackson-datatype-jsr310-2.10.5.jar:/root/tms-realtime/5Au15aHTz9/debezium-ddl-parser-1.6.4.Final.jar:/root/tms-realtime/fMF2viEOPB/antlr4-runtime-4.8.jar:/root/tms-realtime/BFDBFm5ndS/mysql-binlog-connector-java-0.25.3.jar:/root/tms-realtime/FgIh25tiFr/mysql-connector-java-8.0.27.jar:/root/tms-realtime/AzJMXWJjhJ/esri-geometry-api-2.2.0.jar:/root/tms-realtime/x94mNYtoqy/HikariCP-4.0.3.jar:/root/tms-realtime/HHZ8uBnovC/awaitility-4.0.1.jar:/root/tms-realtime/EgxqrMg7Q2/hamcrest-2.1.jar:/root/tms-realtime/xz3BmBKv0n/flink-statebackend-rocksdb-1.16.1.jar:/root/tms-realtime/a069XXp7J2/frocksdbjni-6.20.3-ververica-1.0.jar com.atguigu.tms.realtime.app.ods.OdsApp Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory at org.apache.flink.configuration.Configuration.<clinit>(Configuration.java:68) at org.apache.flink.streaming.api.environment.StreamExecutionEnvironment.getExecutionEnvironment(StreamExecutionEnvironment.java:2373) at com.atguigu.tms.realtime.util.CreateEnvUtil.getStreamEnv(CreateEnvUtil.java:29) at com.atguigu.tms.realtime.app.ods.OdsApp.main(OdsApp.java:26) Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory at java.net.URLClassLoader.findClass(URLClassLoader.java:382) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 4 more 进程已结束,退出代码为 1
最新发布
06-29
Flink 程序在启动时出现 `java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory` 或 `Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory` 异常,表明运行时缺少 SLF4J 的类库支持。Apache Flink 依赖 SLF4J(Simple Logging Facade for Java)作为日志门面,并通常与 Log4j、Logback等具体实现绑定使用。若项目构建或运行环境未正确引入 SLF4J 的 API 和其底层实现,则会抛出此类异常[^1]。 ### 添加 SLF4J API 依赖 确保项目的构建文件中包含 SLF4J 的核心 API: **Maven 示例:** ```xml <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.9</version> </dependency> ``` **Gradle 示例:** ```groovy implementation 'org.slf4j:slf4j-api:2.0.9' ``` ### 添加 SLF4J 实现依赖 Flink 推荐使用 Log4j 作为日志实现,因此还需添加对应的桥接依赖以支持 SLF4J: **Maven 示例:** ```xml <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> </dependency> ``` **Gradle 示例:** ```groovy implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' ``` ### 检查依赖冲突 多个 SLF4J 绑定可能导致日志初始化失败,应避免这种情况。使用以下命令检查依赖树: **Maven:** ```bash mvn dependency:tree ``` **Gradle:** ```bash gradle dependencies ``` 确保只存在一个 SLF4J 实现。例如,如果发现同时存在 `log4j-slf4j-impl` 和 `logback-classic`,则需排除其中一个[^3]。 ### 使用 Fat Jar 打包所有依赖 为确保运行环境中包含所有必需的类库,推荐使用插件将依赖打包进最终的可执行 JAR 文件中。例如使用 Maven Shade Plugin: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals><goal>shade</goal></goals> <configuration> <transformers> <transformer implementation="..."> <mainClass>com.example.MyJob</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> ``` ### 相关问题分析 - 如果项目中并未显式使用 Log4j,但仍然报错,可能是某些第三方库间接引入了对 Log4j 的依赖。此时可通过排除相关依赖来解决。 - 若使用的是 Spring Boot 并且倾向于使用 Logback 而非 Log4j,则应在配置中排除 Log4j 并明确指定使用 Logback 作为 SLF4J 的实现[^3]。 - 另外,有些情况下旧版本的 Flink 或其他组件可能依赖于更老的日志库(如 `log4j-1.2.x`),此时需要手动引入对应版本的依赖以兼容[^2]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FireCoder

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

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

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

打赏作者

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

抵扣说明:

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

余额充值