- 浏览: 581598 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (188)
- java (14)
- web (14)
- web service (3)
- 杂谈 (14)
- Version Control (13)
- software test (30)
- linux (17)
- database (3)
- distributed storage and computing (1)
- ejb (7)
- project building (46)
- spring & IOC (2)
- Thread (2)
- xml (2)
- tool software (0)
- [网站分类]1.网站首页原创Java技术区(对首页文章的要求: 原创、高质量、经过认真思考并精心写作。BlogJava管理团队会对首页的文章进行管理。) (0)
- project manager (9)
- OSGI (1)
- nosql (3)
最新评论
-
sp42:
好搞笑
你懂不懂xml! (2) -
cherishmmo2004:
感觉你们都很牛掰,我们做的一个运维平台也是用karaf的,用k ...
基于osgi开发大型的企业应用 -
liubey:
“自作聪明”的使用了读写锁,其实只使用ReentrantLoc ...
编码最佳实践(4)--小心LinkedHashMap的get()方法 -
liubey:
你这个代码是sublist后仍然一直持有这个sub的引用,一般 ...
编码最佳实践(5)--小心!这只是冰山一角 -
xiegqooo:
初学maven(5)-使用assembly plugin实现自定义打包
依然是近期工作中发现的问题,真实案例,写下来分享给大家。
在开始本文之前,援引同事对此案例的一句评语:sun的程序员也是程序员啊!
开始讲故事吧,依然是performance tuning,还是老伎俩,加压力,做thread dump,然后检查。结果就发现有如下的线程,而且比率极大:dump出来大概总共70-80个工作线程,有5-6个在做这个事情,还有大概50个在等着做这个事情,也就是说大概80%+的工作线程都于此有关。
java.lang.Thread.State: RUNNABLE
at java.util.zip.ZipFile.getEntry(Native Method)
at java.util.zip.ZipFile.getEntry(ZipFile.java: 149 )
- locked < 0xbb09d458 > (a java.util.jar.JarFile)
at java.util.jar.JarFile.getEntry(JarFile.java: 206 )
at java.util.jar.JarFile.getJarEntry(JarFile.java: 189 )
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java: 754 )
at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java: 732 )
at sun.misc.URLClassPath$ 1 .next(URLClassPath.java: 195 )
at sun.misc.URLClassPath$ 1 .hasMoreElements(URLClassPath.java: 205 )
at java.net.URLClassLoader$ 3 $ 1 .run(URLClassLoader.java: 393 )
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader$ 3 .next(URLClassLoader.java: 390 )
at java.net.URLClassLoader$ 3 .hasMoreElements(URLClassLoader.java: 415 )
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java: 27 )
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java: 36 )
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java: 27 )
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java: 36 )
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java: 27 )
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java: 36 )
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java: 27 )
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java: 36 )
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java: 27 )
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java: 36 )
at com.sun.xml.ws.util.ServiceFinder$LazyIterator.hasNext(ServiceFinder.java: 357 )
at com.sun.xml.ws.api.pipe.TransportTubeFactory.create(TransportTubeFactory.java: 129 )
at com.sun.xml.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java: 112 )
at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java: 595 )
at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java: 554 )
at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java: 539 )
at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java: 436 )
- locked < 0xe8e3e200 > (a com.sun.xml.ws.api.pipe.Fiber)
at com.sun.xml.ws.api.pipe.helper.AbstractTubeImpl.process(AbstractTubeImpl.java: 106 )
at com.sun.xml.ws.tx.client.TxClientPipe.process(TxClientPipe.java: 177 )
at com.sun.xml.ws.api.pipe.helper.PipeAdapter.processRequest(PipeAdapter.java: 115 )
at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java: 595 )
at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java: 554 )
at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java: 539 )
at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java: 436 )
- locked < 0xe8e3e200 > (a com.sun.xml.ws.api.pipe.Fiber)
at com.sun.xml.ws.client.Stub.process(Stub.java: 248 )
at com.sun.xml.ws.client.dispatch.DispatchImpl.doInvoke(DispatchImpl.java: 180 )
at com.sun.xml.ws.client.dispatch.DispatchImpl.invoke(DispatchImpl.java: 206 )
at com.sun.jbi.httpsoapbc.OutboundMessageProcessor.outboundCall(OutboundMessageProcessor.java: 1256 )
at com.sun.jbi.httpsoapbc.OutboundMessageProcessor.dispatch(OutboundMessageProcessor.java: 1296 )
at com.sun.jbi.httpsoapbc.OutboundMessageProcessor.processRequestReplyOutbound(OutboundMessageProcessor.java: 747 )
at com.sun.jbi.httpsoapbc.OutboundMessageProcessor.processMessage(OutboundMessageProcessor.java: 257 )
at com.sun.jbi.httpsoapbc.OutboundAction.run(OutboundAction.java: 63 )
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java: 886 )
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 908 )
at java.lang.Thread.run(Thread.java: 619 )
忘了介绍一下背景:openESB运行于 glassfish平台,一个bepl 调用4个webservice.上面的线程HTTPBC-OutboundReceiver 是openESB中作为客户端调用webservice的工作线程,从调用栈中可以看到:
1. task被executor执行
2. jbi中的httpsoapbc用来处理消息
3. com.sun.xml.ws.client使用Stub来处理信息,标准的webservice调用
4. 最重要的两行,我们后面再来看这里发生了什么
at com.sun.xml.ws.api.pipe.TransportTubeFactory.create(TransportTubeFactory.java:129)
at com.sun.xml.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java:112)
5. ServiceFinder,通过Iterator.hasNext在做游历
6. java的classloader在find resource
7. 为了从jar包中装载resouce,需要处理解压缩,java.util.zip被调用
OK,现在整体看,在处理webservice的调用过程中,需要在classpath路径下载入资源,而且看样子这个资源的载入还是很耗cpu时间的。
先看看在装载什么资源,上面的调用明显是jax-ws,glassfish下跑的是metro的实现。在glassfish目录下找到webservice-rt.jar,打开看META-INF/MANIFEST.MF,找到Name: jaxws-rt.jar这段,发现有Implementation-Version: 2.1.3.1,上metro的官网,找metor 相应版本的源文件,最接近的是2.1.3。下载下来查看源码,恩,开源就是好啊。
首先找到ServiceFinder类,代码片段:
private static final String prefix = " META-INF/services/ " ;
if (configs == null ) {
String fullName = prefix + service.getName();
if (loader == null )
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
非常通用的做法,META-INF/services/下查找资源看改用哪个实现。
逆推找到类TransportTubeFactory中对此的调用代码:
for (TransportTubeFactory factory : ServiceFinder.find(TransportTubeFactory. class ,classLoader)) {
Tube tube = factory.doCreate(context);
if (tube != null ) {
TransportTubeFactory.logger.fine(factory.getClass() + " successfully created " + tube);
return tube;
}
}
代码的意图很明显,create()方法在classpath下找到TransportTubeFactory的实现,然后逐个尝试创建Tube对象。
疑问就来了,为什么每次请求都要create, 这里的create明显是重量级的,需要到classpath下去查找一下某个Factory的实现。明显不合理,继续逆推代码,在类DeferredTransportPipe中找到对create的调用:
if (request.endpointAddress == address)
// cache hit
return transport.processRequest(request);
.....
address = request.endpointAddress;
transport = TransportTubeFactory.create(classLoader, newContext);
不出意料的,这里有cache: 如果endpoint address和上次相同,直接重用,否则就调用create方法。从前面的现象看,cache没有命中,至少没有全部命中。
由于我们的测试的案例是连续调用4个不同的webservice,当然每个webservice的endpoint address是不同的。因此第一反应是这里transport cache机制被四个webservice的client端公用,因此每次调用只有1/4的概率和上次相同,其他的3/4就只能重新创建。之后花费了大量时间和精力去查看openesb的代码,过程不提,结果就是无果。
再回头来看这个cache的地方,有点奇怪为什么不命中。好在可以做remote debug,debug进入,到if(request.endpointAddress==address ) 这行,发现果然没有命中,但是随即检查request.endpointAddress和 address的值,非常惊讶的发现里面的实际值是相同的!!
直接晕倒!
值相同而==不成立,那么就是说这里的request.endpointAddress 和 address 并不是一般的enum或者类型安全枚举, ==的检测根本不成立。
这是sun的代码啊,sun的程序员也会犯这种低级错误?用 == 来比较普通对象而不是用equals()方法?
继续看EndpointAddress 这个类,无语了:
1. 这是个普通的类,根本不是enum或者类型安全枚举,有两个publish的构造函数,理论上,使用者可以随意创建任意数量的实例
2. 没有重载equals方法,因此即使改用equals方法来提到==的检查也是无意义的,默认的equals()还是检查对象引用
因此,再来看DeferredTransportPipe中的这段试图重用cache的代码
if (request.endpointAddress == address)
// cache hit
return transport.processRequest(request);
这里的"if(request.endpointAddress==address)"能否成立,完全取决于客户端的调用方法:如果调用方保证每次相同endpointAddress的请求,request.endpointAddress都会是同一个实例,则这里的cache可以命中。否则这个cache毫无意义,还是需要每次重新创建重量级的transport对象。我们的测试案例中,很明显,openESB的程序员,没有考虑到DeferredTransportPipe这里的"特殊"要求,每次调用传入的request.endpointAddress虽然里面的实际值相同,但是每次都是不同的实例。因此 == 不成立,cache不命中。
查找了一下相关的类和接口定义,对于方法public NextAction processRequest(Packet request) 和 Packet中的endpointAddress属性,没有任何javadoc说明要求Packet中的endpointAddress属性需要做到相同地址只使用一个对象实例。
看Packet中的endpointAddress的设值代码:
if (s == null )
this .endpointAddress = null ;
else
this .endpointAddress = EndpointAddress.create(s);
}
public static EndpointAddress create(String url) {
try {
return new EndpointAddress(url);
} catch (URISyntaxException e) {
throw new WebServiceException( " Illegal endpoint address: " + url,e);
}
}
明显每次通过调用setEndPointAddressString()设置时都会产生一个新的EndpointAddress实例。看代码时还意外的发现,
这个endpointAddress属性居然是public的!! 看样子,DeferredTransportPipe类的开发者,是寄希望于调用者不要通过Packet.setEndPointAddressString(String s)来设置,而是希望直接使用public的属性,这样才有希望命中cache!这分明是在挖坑,而且明显现在openESB的开发者被坑进去了!
鄙视啊鄙视,这样的代码,居然是sun的程序员写出来的,还放在metro里面,而metro作为默认的jax-ws实现被放在jdk中...... 很是无语。
无奈之下,修改代码,将 == 去掉,自己简单的判断一下endpointAddress的实际值
&& address.getURI() != null
&& request.endpointAddress.getURI().equals(address.getURI())) {
// cache hit
return transport.processRequest(request);
}
将编译出来的class文件,替换glassfish/lib/webservice-rt.jar中的相同文件,重新测试。再次thread dump,发现问题解决了。
期间看了一下DeferredTransportPipe类的各个version的代码,这里的 == 一直都没有改,难道sun就一直没有发现这里有问题?有兴趣的可以通过下面的地址使用fisheye来查看这个类的代码:
http://fisheye5.cenqua.com/browse/jax-ws-sources/jaxws-ri/rt/src/com/sun/xml/ws/transport/DeferredTransportPipe.java?r1=1.3&r2=1.3.4.1&u=-1
总结:用 == 来比较非enum或者类型安全枚举的对象实例,这种错误一般只有初学者才犯,万万没有想到,在metro这样级别的代码中也能出现。无限感叹啊,再次援引同事的评语作为本文的结束语:
sun的程序员也是程序员啊!
后续更新:
1. 下午做了这个bug解决前后的性能对比,相同机器相同环境
fix前: CPU: 89%-92% TPS:1111 AVG response time:71
fix后: CPU: 69%-71% TPS:1300 AVG response time:61
总结说就是fix之后,tps 提升接近20%,而cpu使用反而降低20%,平均响应时间减少14%。
反过来,看原来的这个bug对性能的影响有多大,如此差异仅仅源于一行代码而已。
2. 提交给sun的tr,24小时了,没有任何反应
好吧,不能指望sun了。
评论
sun也招新手啊,早知道也投个简历过去了:)
...
上metro的官网,找metor 相应版本的源文件,最接近的是2.1.3。
下载下来查看源码,恩,开源就是好啊
开源就是好啊,顶这句。
2、有一个疑问,这个地方到底是bug,还是design
如果是design的话,则metor会要求使用者必须保证,针对每个endpointAddress地址的请求,它的request.endpointAddress必须是同一个实例,否则==无法成立。
这样就要求使用者必须实现以下逻辑,伪代码如下:
HashMap<String, EndpointAddress> cache = ...;
String address = ...
EndpointAddress endpointAddress = cache.get(address);
if (endpointAddress == null) {
endpointAddress = new EndpointAddress ()
cache.put(address , endpointAddress );
}
processRequest(request);
这样才能利用到metro中的cache,避免每次调用create().
如果说是设计的话,就要求使用者必须知道并遵循这个游戏规则,我想基本的javadoc上应该要有所表现。很遗憾没有看到,而且从我们的案例上看,openESB,同为sun旗下的产品,他们没有做到。
如果说是bug的话,那么这个bug也未免大的离谱了点,我们正在联系sun看是否算bug,是否需要fix。毕竟,目前最新的版本依然还是用的是 == 。真是可怕啊!
2、有一个疑问,这个地方到底是bug,还是design
发表评论
-
新博客网站
2014-04-22 23:26 1071开始启用新的博客网站,基于hexo,搭载在github上。 ... -
遭遇drupal keyword search模块bug,不能添加新的页面关键字
2012-07-11 08:00 2233这是个非常无聊而无奈的问题,昨晚在解决globalre ... -
解决drupal的globalrediect模块的重定向循环问题
2012-07-11 07:26 1649昨晚继续折腾俺的小站http://www.javaun ... -
Java University 网站开通过程吐糟
2012-06-24 10:40 1666折腾了两天,终于将Java University这个站 ... -
告别JE
2012-01-02 19:04 293在JE很多年了,看了很多好帖子,认识很多优秀的人。 ... -
写在qcon beijing 2011前夜
2011-04-07 22:40 1822在酒店里上网,无聊又来到je,sorry,我还是习惯这 ... -
被收购之后sun打算放弃开源社区了吗?
2010-05-09 21:41 1372对比最近遇到的两个事情,明显感觉sun有力不从心或者 ... -
基于java的cms系统magnolia安装试用
2010-04-04 16:33 3366最近想找个cms系统来用用,做点简单的东西,因为自己比较熟悉j ... -
一个因参数定义不合理造成的滑稽错误引发的思考
2010-04-17 10:22 1059这是一个真实案例,本周在工作中发现的,案例情况比较极端,因此显 ... -
基于osgi开发大型的企业应用
2010-02-01 16:39 8231前端时间认真的学习了一下osgi相关的知识,个人感觉是 ... -
java资源收集--开源项目
2008-10-21 15:54 1309一些看到过的java资源,包括项目,工具等,因 ... -
转:Google App Engine正式宣布支持Java!
2009-04-08 16:48 1223刚得到的消息,实验了一下可以申请成功,有兴趣的兄弟赶紧。 新 ... -
充分利用8G大内存----Ramdisk的安装设置
2009-07-05 21:23 14062日前升级内存容量到8g之后,发现在xp下因为无法全 ... -
[fun]我们的代码规模比起来还是差得远
2009-07-29 09:45 1296我们的团队 ...
相关推荐
### sun程序员认证参考知识点解析 #### 一、课程目标与内容概览 - **主要目标**:为学习者提供必要的知识与技能,以便能够有效地进行Java应用程序和Applets的面向对象编程。 - **核心内容**:涵盖Java编程语言的...
### SUN JAVA程序员模拟题知识点解析 #### 题目1: JDK组成部分 - **知识点**:JDK(Java Development Kit)组成 - **解析**:JDK主要由以下几个部分组成: - **Java编程语言**:Java编程语言是用于编写Java应用...
Sun认证Java程序员考试,全称为SCJP(Sun Certified Java Programmer),是Oracle公司(原Sun Microsystems)推出的针对Java编程技能的一项专业认证。这个考试主要面向希望验证自己Java编程基础的开发者,旨在评估...
Sun Java 认证考试题库 本题库涵盖了 Java 语言的基础知识和高级知识,包括 Java 入门、数据类型和运算符、流程控制与数组、封装、继承、抽象类与接口、多态、异常、多线程机制、输入输出流、泛型和集合框架、基于 ...
目录 基础知识练习 ................................................................................................................................... 1 第一章练习题(Java入门) ..........................
SUN公司(后被Oracle收购)推出的Java程序员认证课程是全球范围内Java开发者广泛认可的专业资格认证,旨在验证个人对Java语言的深入理解和应用能力。本课程主要针对Java 5.0版本,包括SCJP(Sun Certified ...
### Sun Java程序员模拟题知识点解析 #### 题目1: JDK组成部分 - **知识点**:JDK(Java Development Kit)组成 - **解析**:JDK主要由以下几个部分组成: - **Java编程语言**:这是开发Java应用程序的基础。 - *...
《SUN认证JAVA程序员考试大纲》是一份详细指导Java程序员准备SUN(Sun Microsystems,现已被Oracle收购)官方认证考试的重要文档。这份大纲为学习者提供了清晰的学习路径和重点,帮助他们了解考试涵盖的各个领域,...
Sun公司程序员认证(JAVA)笔试题 本资源涉及到 Java 编程语言的各种知识点,包括基本数据类型、运算符、控制流语句、方法调用、StringBuffer 等。 1. 在题目 11 中,我们看到了一些位运算符的应用。位运算符是 ...
原版CHM文档,内容完整
《Sun认证Java2程序员考试辅导(下册)》是一份专为准备Java程序员考试的考生量身定制的参考资料。这份资料旨在帮助考生深入理解和掌握Java编程语言的核心概念,提升编程技能,以顺利通过Sun认证Java2程序员考试。下面...
《SUN认证JAVA程序员考试大纲》是一份详细指导学习者准备SUN认证JAVA程序员考试的文档。这份大纲旨在帮助学员全面掌握Java编程的核心知识和技能,以顺利通过考试。 课程目标明确,旨在培养学员理解并掌握面向对象...
SUN公司JAVA程序员SCJP证书考试真题(绝对真题)!
《SCJP(Sun认证Java程序员)考试宝典1》是一本专门为准备SCJP认证考试的学员量身定制的复习资料。SCJP,全称为Sun Certified Java Programmer,是Oracle公司(原Sun Microsystems)推出的针对Java编程语言的基础级...
Sun_Java程序员认证考试题.doc
1. **Java概述**:Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems开发,后来被Oracle公司收购。Java设计为具有可移植性、安全性、健壮性和平台无关性的特点。 2. **数据类型**:Java中的数据类型分为...
2019年sun java程序员认证考试题库,这是今年的最新版与旧版有很大的区别可以方便复习、准备考试
Java则是一种完全面向对象的编程语言,由Sun Microsystems(现已被Oracle收购)推出。它具有“一次编写,到处运行”的跨平台特性,因为它的运行依赖于Java虚拟机(JVM)。Java被广泛应用于企业级应用开发、Web应用、...