本文总结了一些Java应用在线常见问题的定位步骤。 分享的主要目的是让接触网上问题较少的同学有一个预认知,以免遇到实际问题时手忙脚乱。 毕竟作者本人也是从匆忙的时代走过来的。
这里只是提醒一下。 请记住,在线应急过程中,总体目标只有一个:“尽快恢复服务,消除影响”。 无论处于哪个紧急阶段,我们首先要考虑的是恢复问题。 恢复问题不一定能定位问题,也不可能有完美的解决方案。 可能是凭经验判断,也可能是预设的开关等,但可能会让问题变得更糟。 我们达到快速恢复的目的,然后保留部分站点,然后再去定位问题,解决问题,复查磁盘。
好的,现在让我们言归正传。
1. CPU 利用率高/激增❝
注:CPU使用率是衡量系统繁忙程度的重要指标。 但“CPU 使用率的安全阈值是相对的,取决于你的系统是 IO 密集型还是计算密集型。” 一般来说,计算密集型应用的CPU占用率高、负载低,而IO密集型应用则相反。
❞
“常见原因:”
模拟
这里为了演示,用最简单的死循环来模拟CPU飙升的场景。 以下是模拟代码,
在最简单的Web项目中添加这个类,
@Component
public class CpuReaper {
@PostConstruct
public void cpuReaper() {
int num = 0;
long start = System.currentTimeMillis() / 1000;
while (true) {
num = num + 1;
if (num == Integer.MAX_VALUE) {
System.out.println("reset");
num = 0;
}
if ((System.currentTimeMillis() / 1000) - start > 1000) {
return;
}
}
}
}
复制代码
打包成jar后,在服务器上运行。 java -jar cpu-.jar &
第一步:定位有问题的线程 方法一:传统方法 top 定位CPU最高的进程 执行top命令,查看所有进程占用系统CPU的顺序,定位是哪个进程造成的。 在本例中,它是我们的 java 进程。 PID 列是进程号。 (不清楚指标含义的请参见【附录】) top -Hp pid 定位CPU最高的线程 '0x%x' tid 将线程id转换为16进制
> printf '0x%x' 12817
> 0x3211
PID | grep tid 查找线程堆栈
> jstack 12816 | grep 0x3211 -A 30
方法b:show-busy-java-
这个脚本来自之前的开源项目,该项目提供了很多有用的脚本,show-busy-java-就是其中之一。 使用这个脚本,可以直接简化方法A中繁琐的步骤。 如下,
> wget --no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release-2.x/bin/show-busy-java-threads
> chmod +x show-busy-java-threads
> ./show-busy-java-threads
show-busy-java-threads
# 从所有运行的Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈
# 缺省会自动从所有的Java进程中找出最消耗CPU的线程,这样用更方便
# 当然你可以手动指定要分析的Java进程Id,以保证只会显示你关心的那个Java进程的信息
show-busy-java-threads -p <指定的Java进程Id>
show-busy-java-threads -c <要显示的线程栈数>
方法c:
阿里开源现在几乎接管了我们在线排查工作,提供了一套非常完整的工具。 在这种场景下,只需要一个-n命令即可。
> curl -O https://arthas.gitee.io/arthas-boot.jar # 下载
❝
需要注意的是, 的 cpu 比率与前面两种 cpu 比率统计方法不同。 前两个针对的是从Java进程启动到现在的CPU占用率。 这是一个采样间隔内当前JVM中每个线程占用的CPU时间占总CPU时间的百分比。
详情见官网:..io/artha s/.html
❞ 后续行动
通过第一步之后,找到有问题的代码之后,观察线程堆栈之后。 我们“需要根据具体问题具体分析”。 这里有一些例子。
情况1:发现GC线程占用CPU最高。
GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fd99001f800 nid=0x779 runnable
GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fd990021800 nid=0x77a runnable
GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fd990023000 nid=0x77b runnable
GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fd990025000 nid=0x77c runnabl
复制代码
关于gc的调查有很多,所以我决定稍后单独写一篇。
情况2:发现业务线程占用CPU最多。 2.频繁GC 1.回顾GC流程
在理解下面的内容之前,请先花点时间回顾一下GC的整个流程。
接续前面的内容,这种情况下,我们很自然的想到去检查gc的具体情况。
这里对开启gc log进行补充说明。 经常讨论(惰性思维)的一个问题是生产环境中是否应该启用GC日志记录。 因为它产生的开销通常非常有限,所以我的答案是它需要“打开”。 但启动JVM时不需要指定GC日志参数。
❝
JVM 有一类特殊的参数,称为可管理参数。 对于这些参数,它们的值可以在运行时修改。 我们在这里讨论的所有参数以及以“”开头的参数都是可管理的参数。 这样我们就可以随时打开或关闭GC日志。 例如,我们可以使用JDK自带的jinfo工具来设置这些参数,或者通过JMX客户端调用的方法来设置这些参数。
这里再赞一个❤️,它提供的命令可以直接查看和更新VM诊断相关的参数。
❞
获取gc log后,可以上传至GC easy帮助分析,获得可视化图表分析结果。
2、GC的原因及部位
” “
从S区提升的对象不能放入老年代(如果fgc回收无效,会抛出OOM)。
可能的原因:
也可以从full GC的效果来推断问题所在。 正常情况下,一次full GC应该回收大量内存,所以“正常的堆内存曲线应该是锯齿形的”。 如果发现full GC后堆内存几乎没有下降,可以推断:“堆中存在大量无法回收的对象,并且在不断膨胀,使得堆使用率超过full GC触发阈值,但无法回收,导致一直执行full GC。”也就是说,可能是“内存泄漏”。
一般来说,GC相关的异常推断需要涉及到“内存分析”,使用jmap等工具转储内存快照(或)命令,然后使用MAT、、等可视化内存分析工具。
至于内存分析之后的步骤,需要根据具体问题来分析。
3.线程池异常
Java线程池以有界队列的线程池为例。 当提交新任务时,如果运行的线程少于2个,则会创建一个新线程来处理该请求。 如果正在运行的线程数等于 ,则将新任务添加到队列中,直到队列满。 当队列满时,会继续开辟新的线程来处理任务,但不能超过。 当任务队列满了,并且已经开启了最大线程数,此时又有新的任务到来,就会拒绝服务。
常见问题及原因
此类线程池异常一般有以下几种原因:
“下游服务响应时间RT过长” 这种情况可能是下游服务异常造成的。 作为消费者,我们需要设置合适的超时时间和熔断降级机制。 另外,针对这种情况,一般需要有相应的监控机制:比如日志监控、监控报警等,不要等到目标用户感觉到异常、从外部反映了问题才去检查日志。 “数据库慢sql或数据库死锁”
查看日志关键字
“Java 代码死锁” –l pid | grep -i –E ' | '
前两个问题的排查方法一般是通过查看日志或者一些监控组件。
4.常见问题恢复❝
这部分内容参考自这篇文章
❞
五,
这里我还是想单独一节来使用这个工具。
它是阿里巴巴开源的Java诊断工具。 基于Java Agent方法,通过该方法修改字节码来进行Java应用诊断。
❝
以上内容摘自官方文档。
❞
另外,它还集成了ognl这个轻量级的表达引擎,通过ognl,你可以用它来实现很多“展示”操作。
其他的我这里就不多说了。 如果有兴趣可以查看官方文档和issue。
6. 涉及的工具
我们来谈谈一些工具。
结语
我知道我的这篇文章对线上异常的总结并不全面,很多异常场景如“网络(超时、TCP队列溢出...)”、堆外内存等都没有涵盖。主要原因是我有接触的很少,也没有深入的了解。 如果强行写出来,几乎毫无意义,而且还怕别人误解。
另外我想说的是,网上考察Java应用其实很讲究一个人的基础是否扎实,解决问题的能力是否良好。 比如线程池运行机制、gc分析、Java内存分析等等,如果基础不扎实的话会比较混乱。 另外,多看网上一些关于异常排查的好的经验文章,这样即使暂时没有遇到,也会在脑海中慢慢总结出一套解决类似问题的结构框架。 当你真正遇到他们的时候,只是类比的问题。
❝
如果这篇文章对你有帮助,希望你能点赞,这就是对我最大的动力