解决JVM的CPU使用率高
问题:每天的系统自动计算都会报CPU使用率过高短信报警与邮件报警
最终问题定位:vendor信息同步redis中代码 for循环中使用list.contains()线性查找判断sku是否存在,该代码块的时间复杂度近似O(n*m)导致线程并发情况下CPU耗用高
解决方案:把list数据放入HashSet判断sku是否存在,把CPU使用率从90+%降到了10+%及以下
通常的解决思路是:
top
命令找出有问题Java
进程及其线程号:- 开启线程显示模式
- 按
CPU
使用率排序 - 记下
Java
进程号及其CPU
高的线程号
- 手动转成十六进制(可以用
printf %x 1234
)。 jstack
有问题的Java
进程。grep
十六进制的线程id
,找到线程栈,查看关联的问题代码块。
参见:https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#beer-show-busy-java-threadssh
本次解决时,基本知道哪个代码模块Job有问题,同时无法访问线上系统环境(可以申请),所以省略了大部分步骤,只使用了的jstack来解决问题。
本次解决思路:
1、根据报警时间与当时正在跑的Job,定位到具体JOB,当时只有VENDOR一个Job在跑
2、晚上跑vendor信息同步redis,在cpu使用率高时,选择一台机器,执行 jstack -l pid命令,并导出栈信息
3、查看栈信息,找到具体的线程栈,查看当时栈都在执行什么操作,发现vendor的10个线程所有Thread栈都在执行com.calc.redis.adaptor.VendorSKURedisAdaptor.getValue(VendorSKURedisAdaptor.java:66)
4、分析具体的代码,基本定位到使用list.contains()线性查找导致CPU使用过高
5、将上段代码由list改为HashSet,上线测试,CPU使用率正常,问题解决。如果CPU还高的话就需要循环以上步骤,改进耗CPU的代码
Tips:解决问题过程中发现的一个有关线程池的未合理使用场景
定位到具体job,分析相应时间段的任务队列表时,发现CPU高集中报在Job会连续占用多个id相连的任务阶段。线程池固定大小是10,队列固定也是10,RejectedExecutionHandler策略使用的是CallerRunsPolicy,当CallerRunsPolicy生效跑的过程中,线程池不会放入新的任务,使得CallerRunsPolicy执行完后线程池队列基本是空的,未能充分使用线程资源。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.CallerRunsPolicy());
这时需要考虑如何合理的使用线程资源,诸位自己想想吧。