用“逐步排除”的方法定位Java服务线上“系统性”故障
一、摘要
二、导言
三、本方法适用的范围
四、有哪些异常现象
- Java堆满
Java堆是“Java虚拟机”从操作系统申请到的一大块内存,用于存放Java程序运行中创建的对象。当Java堆满或者较满的情况下,会触发“Java虚拟机”的“垃圾收集”操作,将所有“不可达对象”(即程序逻辑不能引用到的对象)清理掉。有时,由于程序逻辑或者Java堆参数设置的问题,会导致“可达对象”(即程序逻辑可以引用到的对象)占满了Java堆。这时,Java虚拟机就会无休止地做“垃圾回收”操作,使得整个Java程序会进入卡死状态。我们可以使用jstat工具查看Java堆的占用率。 - 日志中的异常
目标服务可能会在日志中记录一些异常信息,例如超时、操作失败等信息,其中可能含有系统故障的关键信息。 - 疑难杂症
死锁、死循环、数据结构异常(过大或者被破坏)、集中等待外部服务回应等现象。这些异常现象通常采用jstack工具可以获取到非常有用的线索。
五、故障定位的步骤
第一步:排除其它程序占用过量系统资源的情况
图示:排除其它程序占用过量系统资源的情况
如果idle较少,则按shift+p,将进程按照CPU占用率从高到低排序,逐一排查(见下面TIP)。
注:centos7没有展示单独的-/+ buffers/cache行,可以用free -h看看,参考:使用Free命令查看Linux服务器内存使用状况(-/+ buffers/cache详解)
如果剩余物理内存较少(例如剩余1GB以下),则运行【vmstat -n 1】检查si/so(换入换出)情况,
第一行数值表示的是从系统启动到运行命令时的均值,我们忽略掉。从第二行开始,每一行的si/so表示该秒内si/so的block数。如果多行数值都为零,则可以排除物理内存不足的问题。如果数值较大(例如大于1000 blocks/sec,block的大小一般是1KB)则说明存在较明显的内存不足问题。我们可以运行【top】输入shift+m,将进程按照物理内存占用(“RES”列)从大到小进行排序,然后对排前面的进程逐一排查(见下面TIP)。
第一组数据是从该机器从开机以来的统计值。从第二组开始,都是每秒钟的统计值。通过【df】命令,可以看到Device与目录的关系。下图设备“sdb”就对应了目录“/disk2”。
假如发现目标服务所在磁盘读写量明显超过推算值,则应该找到大量读写磁盘的进程(见下面TIP)
- 系统提供的调用栈的转储工具【pstack】,可以了解到程序中各个线程当前正在干什么,从而了解到什么逻辑占用了CPU、什么逻辑占用了磁盘等
- 系统提供的调用跟踪工具【strace】,可以侦测到程序中每个系统API调用的参数、返回值、调用时间等。从而确认程序与系统API交互是否正常等。
- 系统提供的调试器【gdb】,可以设置条件断点侦测某个系统函数调用的时候调用栈是什么样的。从而了解到什么逻辑不断在分配内存、什么逻辑不断在创建新连接等
第二步:排除目标服务占用了过量系统资源的情况
图示:排除目标服务占用了过量系统资源的情况
- 如果CPU使用分散到多个线程,而且每个线程占用都不算高(例如都<30%),则排除CPU占用过高的问题
- 如果CPU使用集中到一个或几个线程,而且很高(例如都>95%),则用【jstack pid > jstack.log】获取目标服务中线程调用栈的情况。top中看到的占用CPU较高的线程的PID转换成16进制(字母用小写),然后在jstack.log中找到对应线程,检查其逻辑:
- 假如对应线程是纯计算型任务(例如GC、正则匹配、数值计算等),则排除CPU占用过高的问题。当然如果这种线程占用CPU总量如果过多(例如占满了所有核),则需要对线程数量做控制(限制线程数 < CPU核数)。
- 假如对应线程不是纯计算型任务(例如只是向其他服务请求一些数据,然后简单组合一下返回给用户等),而该线程CPU占用过高(>95%),则可能发生了异常。例如:死循环、数据结构过大等问题,确定具体原因的方法见下文“第三步:目标进程内部观察”。
#查看 cpu 占用率高的线程,XXX为某个程序信息 ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu |grep XXXX #如果占用CPU较高的进程号是21125 而里面占用CPU较高的线程是21233 #10进制转16进制 [root@node211 ~]# printf "%x\n" 21233 52f1 #打印java栈 输出线程21233后面30行的信息 [root@node211 ~]# jstack 21125 |grep -A 30 52f1
第三步:目标服务内部观察
图示:目标服务内部观察
- 检查jstack.log中是否有deadlock报出,如果没有则排除deadlock情况。
Found one Java-level deadlock:=============================“Thread-0″:waiting to lock monitor 0x1884337c (object 0x046ac698, a java.lang.Object),which is held by “main”“main”:waiting to lock monitor 0x188426e4 (object 0x046ac6a0, a java.lang.Object),which is held by “Thread-0″Java stack information for the threads listed above:===================================================“Thread-0″:at LockProblem$T2.run(LockProblem.java:14)- waiting to lock <0x046ac698> (a java.lang.Object)- locked <0x046ac6a0> (a java.lang.Object)“main”:at LockProblem.main(LockProblem.java:25)- waiting to lock <0x046ac6a0> (a java.lang.Object)- locked <0x046ac698> (a java.lang.Object)Found 1 deadlock.
第一种:使用notepad或者UE进行关键字汇总
第二种:通过grep、wc命令汇总
[root@node211 ~]# jstack 21145 |grep "waiting on condition"|wc -l 77 [root@node211 ~]# jstack 21145 |grep "deadlock"|wc -l 0 [root@node211 ~]# jstack 21145 |grep "waiting for monitor entry"|wc -l 0 [root@node211 ~]# jstack 21145 |grep "in Object.wait() "|wc -l 25 [root@node211 ~]# jstack 21145 |grep "runnable "|wc -l 57 [root@node211 ~]# jstack -l 21145 |grep java.lang.Thread.State | awk '{print $2$3$4$5}' | sort | uniq -c 51 RUNNABLE 16 TIMED_WAITING(onobjectmonitor) 18 TIMED_WAITING(parking) 3 TIMED_WAITING(sleeping) 2 WAITING(onobjectmonitor) 53 WAITING(parking)
第三种:在线分析网站:
通过jstack.log.summary中的情况,我们可以较迅速地定位到一些嫌疑点,并可以猜测其故障引起的原因(后文有jstack.log.summary情况举例供参考)
情况 | 嫌疑点 | 猜测原因 |
线程数量过多 | 某种线程数量过多 |
运行环境中“限制线程数量”的机制失效
|
多个线程在等待一把锁,但拿到锁的线程在做某个操作
|
拿到这把锁的线程在做网络connect操作
|
被connect的服务异常 |
|
拿到锁的线程在做数据结构遍历操作
|
该数据结构过大或被破坏
|
某个耗时的操作被反复调用
|
某个应当被缓存的对象多次被创建
|
对象池的配置错误 |
等待外部服务的响应
|
很多线程都在等待外部服务的响应
|
该外部服务故障
|
|
很多线程都在等待FutureTask完成,而FutureTask在等待外部服务的响应
|
该外部服务故障
|
1000 threads at“Timer-0″ prio=6 tid=0x189e3800 nid=0x34e0 in Object.wait() [0x18c2f000]java.lang.Thread.State: TIMED_WAITING (on object monitor)at java.lang.Object.wait(Native Method)at java.util.TimerThread.mainLoop(Timer.java:552)- locked [***] (a java.util.TaskQueue)at java.util.TimerThread.run(Timer.java:505)
38 threads at“Thread-44″ prio=6 tid=0×18981800 nid=0x3a08 waiting for monitor entry [0x1a85f000]java.lang.Thread.State: BLOCKED (on object monitor)at SlowAction$Users.run(SlowAction.java:15)- waiting to lock [***] (a java.lang.Object)
1 threads at“Thread-3″ prio=6 tid=0x1894f400 nid=0×3954 runnable [0x18d1f000]java.lang.Thread.State: RUNNABLEat java.util.LinkedList.indexOf(LinkedList.java:603)at java.util.LinkedList.contains(LinkedList.java:315)at SlowAction$Users.run(SlowAction.java:18)- locked [***] (a java.lang.Object)
99 threads at“resin-tcp-connection-*:3231-321″ daemon prio=10 tid=0x000000004dc43800 nid=0x65f5 waiting for monitor entry [0x00000000507ff000]java.lang.Thread.State: BLOCKED (on object monitor)at org.apache.commons.dbcp.PoolableConnectionFactory.makeObject(PoolableConnectionFactory.java:290)- waiting to lock <0x00000000b26ee8a8> (a org.apache.commons.dbcp.PoolableConnectionFactory)at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:771)at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:95)…1 threads at“resin-tcp-connection-*:3231-149″ daemon prio=10 tid=0x000000004d67e800 nid=0x66d7 runnable [0x000000005180f000]java.lang.Thread.State: RUNNABLE…at org.apache.commons.dbcp.DriverManagerConnectionFactory.createConnection(DriverManagerConnectionFactory.java:46)at org.apache.commons.dbcp.PoolableConnectionFactory.makeObject(PoolableConnectionFactory.java:290)- locked <0x00000000b26ee8a8> (a org.apache.commons.dbcp.PoolableConnectionFactory)at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:771)at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:95)at …
100 threads at
“Thread-0″ prio=6 tid=0x189cdc00 nid=0×2904 runnable [0x18d5f000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:150)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
…
at RequestingService$RPCThread.run(RequestingService.java:24)
100 threads at“Thread-0″ prio=6 tid=0×18861000 nid=0x38b0 waiting on condition [0x1951f000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for [***] (a java.util.concurrent.FutureTask$Sync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:994)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1303)at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:248)at java.util.concurrent.FutureTask.get(FutureTask.java:111)at IndirectWait$MyThread.run(IndirectWait.java:51)100 threads at“pool-1-thread-1″ prio=6 tid=0x188fc000 nid=0×2834 runnable [0x1d71f000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:150)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
…
at IndirectWait.request(IndirectWait.java:23)
at IndirectWait$MyThread$1.call(IndirectWait.java:46)
at IndirectWait$MyThread$1.call(IndirectWait.java:1)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
对于jstack日志,我们要着重关注如下关键信息
Deadlock:表示有死锁
Waiting on condition:等待某个资源或条件发生来唤醒自己。具体需要结合jstacktrace来分析,比如线程正在sleep,网络读写繁忙而等待
Blocked:阻塞
Waiting on monitor entry:在等待获取锁
如果说系统慢,那么要特别关注Blocked,Waiting on condition
如果说系统的cpu耗的高,那么肯定是线程执行有死循环,那么此时要关注下Runable状态。
备注:对比正常系统jstack日志和异常时jstack日志、直接jstack日志里搜索业务代码相关的日志,比如搜索:com.xxx.xxx 这是个好办法
4、另外进程打开的文件数也是一个重要指标
查看打开文件数最多进行,top6
[root@node208 bc]# lsof -Ki|awk '{print $2}'|sort|uniq -c|sort -n -r|head -6 1593 8433 130 1460 95 969 91 1215 86 23883 65 1
为方便读者使用,将故障定位三个步骤的图合并如下:
图示:故障定位步骤汇总