一.事件:刚接手的项目因消息队列堵塞频繁报警,交接的时候原负责人说项目是最近出现的问题,暂时无法解决,只能靠重启解决;
二.排查:因公司用的是jre,也没有其他第三方工具,只能按照经验去逐步去分析。分析处理过程:使用线程池方式异步处理消息队列堵塞问题后,又发现CPU100%导致服务器直接卡死,正常情况下系统会根据系统资源限制来强杀掉进程,经过ulimit发现栈大小限制size直接是机器内存,修正后又出现cpu打满导致服务宕机,继续排查发现主要是因为内存不足,频繁GC导致的,因此确定优化的方向是内存。
三.优化点:
1.优化系统资源限制:服务资源用尽卡死,使用ulimit命令查看资源限制,发现设置栈大小过大可能导致内存不足,cpu打满的情况下系统不会杀掉进程,直接卡死,因此设置栈大小为8MB(默认);stack size (kbytes, -s):栈大小限制,即一个进程可以使用的栈空间大小。
注意点:(1)栈大小过大可能会导致资源浪费、性能下降、栈溢出风险、系统稳定性问题、安全风险如缓冲区溢出攻击;
(2)栈大小过小可能导致StackOverflowError。
2.JVM参数调优:服务内存达到最大,频繁触发gc运行导致cpu飙高,调优参数如下:
-Xms 初始堆内存大小,-Xmx最大堆内存,
GC:-XX:+UseG1GC使用G1垃圾收集器,-XX:MaxGCPauseMillis=100最多暂停时间
-XX:InitiatingHeapOccupancyPercent=45触发GC的堆占用百分比
开启GC日志:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/gc.log
JVM遇到OutOfMemoryError错误时,自动生成堆转储文件:-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./gclogs/dump;
3.统一使用全局单例线程池:项目中在方法中创建线程池,导致每次请求都会创建线程池,虽然创建线程池本身并不会直接占用CPU资源,而且占用内存以及线程管理开销也很小,但是请求量很大时,也将会迅速消耗系统资源,并且线程池中的线程执行任务时会产生CPU占用,统一使用全局线程池,合理配置线程池大小,可以确保系统资源的有效利用;请求消息队列放入线程池中处理,实现异步响应;
4.优化静态对象:项目中使用了比较多的静态集合,如hashmap,而且还有定时任务写入,整理业务 逻辑,调整cron,并进行clear(),注意clear只是清空了集合中的元素,但不会改变其容量或内存占用,因此,如果想要确保旧的集合实例占用内存被释放,可以把集合置为null,这样就的实例就没有任何引用,垃圾收集器就可以回收,然后重新创建新的实例,重新根据当前的需求分配内存空间。
四.主要原因:项目中有个定时任务每半小时获取大概14万新闻,并且是增量获取到内存,项目中对这些数据还进行了大量复杂计算,最近新闻突增了三四倍,增量到内存,导致内存不足。
五.其他:
1.JDK8的ConcurrentHashMap也会造成CPU 100%
原来,JDK8的ConcurrentHashMap也会造成CPU 100%
JDK8下,触发computeIfAbsent的BUG,会造成CPU100% · Issue #2294 · alibaba/spring-cloud-alibaba · GitHub
2.JDK自带工具
https://blog.51cto.com/u_12204/6843813
jps:查看本机java进程信息
jstack:打印线程的栈信息,制作 线程dump文件
jmap:打印内存映射信息,制作 堆dump文件
jstat:性能监控工具
jhat:内存分析工具,用于解析堆dump文件并以适合人阅读的方式展示出来
jconsole:简易的JVM可视化工具
jvisualvm:功能更强大的JVM可视化工具
javap:查看字节码