Java服务端程序“假死”怎么办?( 二 )


文章插图
jstack命令部分结果
上图是jstack命令的部分结果,显示名为logback-8的线程此刻正在等待状态,未执行实际的任务 。
2.2 诊断三把斧JVM进程假死其实就是线程不够用了,忙不过来,用以下三把斧依次施行 , 基本上能够覆盖90%以上场景 。
2.2.1 看进程
首先 , 使用top命令对服务器的负载和进程的资源使用做一个评估 。尤其关注top命令中的负载指标和进程的CPU、内存使用率 。负载代表服务器的繁忙程度,它综合了CPU、IO等指标,负载/CPU数 > 1 代表服务器非常繁忙 。当系统负载高时 , 而每一个进程的CPU都不高时,要考虑服务器是否已经过载 , 比如运行进程过多、IO瓶颈等 。当某个进程的CPU使用高时 , 问题大概率出在该进程中,即可对该进程进行探查 。(本文只讨论JVM,故进程特指JVM进程)
2.2.2 看内存
使用jstat命令查看JVM的内存状态 。当JVM内存泄漏导致堆内存无法回收、堆内存不够用从而触发频繁的Full GC时,JVM会停顿程序的执行进行全面的垃圾回收(stop the world),此时程序将无法响应请求,GC线程会占用大量的CPU时间 。当这种情况发生时 , 会出现进程CPU使用率很高 , 接近系统极限 。堆内存中老年代空间使用比例接近或达到100% , FGC、FGCT在持续地上升(正常情况下Full GC几小时甚至几天才执行一次) 。
当判断为进程因内存问题而一直在Full GC时,可以使用jmap命令将内存dump下来进行进一步的分析,查找内存泄漏的对象,进而进一步找到内存泄漏点 。此外,值得注意的一点是如果堆内存本身设得很小 , 而程序需要的内存又很多,JVM会尝试去回收内存,也会出现频繁的Full GC而导致CPU使用率升高的情况 。这就需要具体问题具体分析了 。
2.2.3 看线程
使用jstack命令查看线程状态 。当操作系统、堆内存都无明显异常情况下,需要进一步探查每一个线程都在做什么 。线程无法处理更多任务,要么是线程都很忙(单核 CPU 100%),要么是都在等待(CPU使用率不高) , 极少数是处于死锁状态,而这些都可以从jstack的命令结果中看出来 。
jstack会显示每个线程当前所处的程序栈 。如果多个线程处在同一个程序栈,那么要引起高度注意,可以通过连续几个间隔10秒左右的jstack结果查看该线程是否一直处于同一位置 , 如果是那么大概率是卡在了这里 。如果是死循环 , 那么CPU使用率会很高,如果是等待IO,那么CPU使用率会很低 。通过卡住的程序栈,可以较容易定位到代码行并进行进一步的分析了 。
Part 03、 案例实战 了解完基本的理论知识 , 下面通过实际工作中碰到的案例来使用三把斧进行分析 。
3.1 案例一:IO等待系统表现:界面无响应,请求API也无响应 。
看进程:top命令显示系统负载很低,进程CPU、内存使用也无异常 。
看内存:jstat命令显示堆内存表现正常:

Java服务端程序“假死”怎么办?

文章插图
jstat显示堆内存状态正常
看线程:jstack命令显示绝大部分线程都卡在同一个地方:
Java服务端程序“假死”怎么办?

文章插图
出现io等待的线程
通过进一步检查AbstractProvinceServiceImpl.sendPost方法,发现是使用java原生的HttpUrlConnection来实现Http请求的方法,而该方法未设置超时时间 , 如果被调用方未返回会一直等待 。
Java服务端程序“假死”怎么办?

文章插图
有缺陷的代码
至此,问题原因查明:因代码缺陷,在发起http请求时未设置超时时间,而恰好此时被调用方系统故障,无法及时返回 , 从而导致线程都卡在这里等待IO,无更多的线程来响应其他请求了 。
3.2 案例二:死循环系统表现:系统响应很慢,api请求有一定的错误率 。
看进程:top命令显示系统负载较高(1核CPU),进程CPU达到190% , 系统过载 。
Java服务端程序“假死”怎么办?

文章插图
负载较高,进程CPU使用率较高
看内存:jstat命令显示堆内存表现正常,无明显的内存问题 。
看线程:多次间隔执行jstack命令,发现一部分进程一直卡在同一个地方:
Java服务端程序“假死”怎么办?

文章插图
出现死循环的线程
通过查看相关代码发现这里有个死循环,当变量cur一直不为null时,程序无法跳出循环 。通过更进一步的分析,最终找数据库中的一条数据其CateParentId是自己,从而触发这个死循环的bug 。


推荐阅读