一个Java应用频繁抛异常导致cpu us诡异现象的案例

标签: 高性能 异常 cpu us诡异现象 Java案例 | 发表时间:2012-10-12 21:13 | 作者:bluedavy
出处:http://blog.bluedavy.com

某Java应用在生产环境运行时cpu us呈现了非常诡异的现象,在qps(每秒请求数)基本稳定的情况下,cpu us会进入一个上涨–迅速下降然后又上涨的循环中,我第一眼看到这cpu us的图的时候都以为是java gc的图,于是着手排查是什么原因造成的。

1、用perf看看是什么地方在耗cpu,跟踪了一段时间后,看到主要是StringTable::intern在消耗cpu,幸运的是淘宝JVM团队之前在做一个应用的优化的时候,也碰到了StringTable::intern是cpu us消耗的主要因素的现象,当时排查的原因是应用里抛异常比较多造成的,于是同样按照这个思路继续;
至于为什么StringTable::intern会变得耗cpu,这个原因不太清楚,在JavaOne2012的《Advanced JVM Tuning》 slide里有讲到StringTable默认的大小是1009,超过了这个大小后性能会大幅度下降,在《Look into the JVM Crystal Ball》里面的footprint的提升部分,也讲到了希望将来改造为Dynamic resize StringTable。

2、Taobao自己的JDK支持输出某段时间内抛出的最多次数的异常,以及抛出异常的堆栈,于是在应用上打开了这个开关进行收集,看到应用会频繁抛出NoSuchMethodException,堆栈信息如下:
java.lang.Throwable.fillInStackTrace(Native Method)
java.lang.Throwable.(Throwable.java:196)
java.lang.Exception.(Exception.java:41)
java.lang.NoSuchMethodException.(NoSuchMethodException.java:32)
java.lang.Class.getMethod(Class.java:1605)
org.apache.commons.beanutils.MethodUtils.getMatchingAccessibleMethod(MethodUtils.java:957)
org.apache.commons.beanutils.MappedPropertyDescriptor.getMethod(MappedPropertyDescriptor.java:409)

3、用Btrace跟踪更为详细的堆栈信息,以及到底是什么方法找不到,BTrace脚本如下:
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;

@BTrace public class Trace{
@OnMethod(
clazz=”java.lang.NoSuchMethodException”,
method=””
)
public static void traceExecute(String s){
println(“who call”);
println(s);
jstack();
}
}
挂上Btrace后很快跟踪到了找不到的方法(而且确实也能看到,NoSuchMethodException抛的是比较的频繁),最初我怀疑是应用里面有多个冲突的版本的类,导致method找不到,于是打开-XX:+TraceClassLoading,确认需要找的那个类是从哪加载的,然后反编译对应的jar包,发现确实是没有相应的方法,因为是业务代码中调BeanUtils时出现的现象,我的猜测是页面请求中提交了这样的参数,但在POJO类中相应的属性已经被删除了,于是把这个猜测提交给了应用开发的同学。

4、应用的开发同学确认是由于页面上form里会有一些隐藏域,而这些隐藏域在这个POJO类是没有对应的属性的,但有点奇怪的是,为什么外部捕捉不到这个异常,跟踪BeanUtils(1.8.3)的代码,才发现BeanUtils在出现NoSuchMethodException的时候,会直接吃掉异常:
950 try {
951 // Check the cache first
952 Method method = getCachedMethod(md);
953 if (method != null) {
954 return method;
955 }
956
957 method = clazz.getMethod(methodName, parameterTypes);
958 if (log.isTraceEnabled()) {
959 log.trace(“Found straight match: ” + method);
960 log.trace(“isPublic:” + Modifier.isPublic(method.getModifiers()));
961 }
962
963 setMethodAccessible(method); // Default access superclass workaround
964
965 cacheMethod(md, method);
966 return method;
967
968 } catch (NoSuchMethodException e) { /* SWALLOW */ }
到这就彻底能解释通了,于是着手做解决方案,在这里采用的解决方法是:写了一个static类,在启动时即把需要用到的相应的POJO类有的属性装载到一个map里,然后在每次调用BeanUtils之前,先判断下属性是否存在,不存在则跳过,这样就不会触发NoSuchMethodException了。

5、改造完成上线后,效果非常理想,cpu us在升级前和升级后的对比状况图示如下:

另外一个数据是压测数据,在升级前当引5倍流量进行压测时,cpu us就会到80%左右,rt接近1s,升级后引10倍流量压测,cpu us也才13%左右,rt在100ms以下,可见效果非常明显。

总结:从这个案例可以看到,Java应用里频繁抛异常对于高并发而言影响还是非常的大,因此在编写Java应用时,还是要尽可能减少抛异常,尤其是吃异常这种事情,要少做。

相关 [java 应用 异常] 推荐:

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

Java自定义异常在项目中的应用

- - Java - 编程语言 - ITeye博客
在Java的一些项目中,在需要提供对外接口时,常常会有必要自定义响应一些code和message(例:0000:Success,500:Error),特别是在对接移动端项目中最为常见. 为更加方便提供这些接口的程序员的开发,可以应用Java的自定义异常处理来实现. 现有一移动端应用,需要对接我们项目,其中有一个用户登录接口,其接口的请求和响应参数如下:.

浅谈java异常

- - 移动开发 - ITeye博客
在《java编程思想》中这样定义 异常:阻止当前方法或作用域继续执行的问题. 虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常. 绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败. 之所以java要提出异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意.

Java异常处理策略

- - 研发管理 - ITeye博客
任务与预先设定的规则不相符的情况都可以称之为异常. 但凡业务逻辑操作,都会划定一些边界或规则,但是往往事与愿违,总会有调皮鬼来挑战系统的健壮性. 用户并不都是知道潜规则的,比如用户的银行账户中只有100块钱,但是用户不查询就直接取200块. 码农有时候太过自信了,比如你在编写文件下载功能时忽略了文件有可能不存在这个分支流程.

一个Java应用频繁抛异常导致cpu us诡异现象的案例

- - BlueDavy之技术Blog
某Java应用在生产环境运行时cpu us呈现了非常诡异的现象,在qps(每秒请求数)基本稳定的情况下,cpu us会进入一个上涨–迅速下降然后又上涨的循环中,我第一眼看到这cpu us的图的时候都以为是java gc的图,于是着手排查是什么原因造成的. 1、用perf看看是什么地方在耗cpu,跟踪了一段时间后,看到主要是StringTable::intern在消耗cpu,幸运的是淘宝JVM团队之前在做一个应用的优化的时候,也碰到了StringTable::intern是cpu us消耗的主要因素的现象,当时排查的原因是应用里抛异常比较多造成的,于是同样按照这个思路继续;.

Java应用运维

- - BlueDavy之技术blog
对于互联网产品或长期运行的产品而言,运维工作非常重要,尤其是在产品复杂了以后,在这篇blog中就来说下Java应用的运维工作(ps:虽然看起来各种语言做的系统的运维工作都差不多,但细节上还是会有很多不同,so本文还是只讲Java的). 苦逼的码农按照需求开发好了一个全新的Java Web应用,该发布上线给用户用了,要把一个Java Web应用发布上线,首先需要搭建运行的环境,运行的环境需要有JDK、APPServer,在已经装好了os的机器上装上JDK和APPServer,开发好的Java Web应用可以用maven直接打成war或ear,将这个打好的包scp或其他方式到目标机器上,准备妥当,就差启动了.

Java异常处理的陋习展播

- - 博客 - 伯乐在线
注:本文来自志军(@ _Zhijun )在微博推荐的一篇转载于2007年09月27的旧文,他说“没法找到原作者”. 的确,我也花了半个多小时在找原作者. 先是找到了一个标注“2005年6月18日转字Java研究组织”的文章(“Java研究组织”的域名已过期),后来找到是大连理工大学碧海青天BBS上一个发于的2003年5月中旬的 合集帖子,其中提到一条来自 CSDN 的链接,可惜该链接已挂,否则应该能找到作者的.

Java异常的性能分析

- - Java译站
在Java中抛异常的性能是非常差的. 通常来说,抛一个异常大概会消耗100到1000个时钟节拍. 通常是出现了意想不到的错误,我们才会往外抛异常. 也就是说,我们肯定不希望一个进程一秒钟就抛出上千个异常. 不过有时候你确实会碰到有些方法把异常当作事件一样往外抛. 我们在 这篇文章中已经看到一个这样的典范):sun.misc.BASE64Decoder之所以性能很差就是因为它通过抛异常来对外请求道,”我还需要更多的数据“:.

Java Socket常见异常处理

- - BlogJava-qileilove
java网络编程Socket通信中,通常会遇到以下异常情况:.   第1个异常是 java.net.BindException:Address already in use: JVM_Bind.   该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时.

Java线程池应用

- - CSDN博客架构设计推荐文章
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务. 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机). Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.