目录
3.2.1.1 案例1: 查看是否有waiting和timed_waiting等信息
3.2.1.2 案例2: 查看是否有“VM Thread” 虚拟机GC回收线程
3.2.1.3 案例3: 查看是否有“deadlock”关键字的信息
3.2.2.2 案例2:jmap -heap 是否存在堆内存要溢出
3.2.3 上下文切换:cs(context switch)一列则代表了上下文切换的次数
4.2.2.2 案例2:Java heap space内存溢出
一 线上故障排查概述
1.1 概述
线上故障排查一般从cpu,磁盘,内存,网络这4个方面入手;
二 磁盘的排查
2.1 磁盘排查
2.1.1 查看磁盘使用情况
1.使用 df -hl 命令来查看磁盘使用情况
2.1.2 查看磁盘读写性能
1.从读写性能排查:iostat -d -k -x命令来进行分析
最后一列%util可以看到每块磁盘写入的程度,而rrqpm/s以及wrqm/s分别表示读写速度,一般就能帮助定位到具体哪块磁盘出现问题了。
2.1.3 iotop命令定位文件读写来源
1.iotop命令来进行定位文件读写的来源
输入:iotop ,显示如下:
2.图中第一列的tid为线程id,这里为1818,需要转成pid(进程id),可以使用命令:
【readlink -f /proc/*/task/tid/../.. 】查找对应的进程id;
readlink -f /proc/*task/1818/../..
如下图所示:这里可以看到通过线程的id 1818 找到进程id 1681 ;
3. 这里看到查找到的pid为1681.找到pid后,使用 cat /proc/pid/io 就可以看这个进程具体的读写情况。
4. 使用命令lsof,lsof -p pid 查看具体进程打开的文件列表 ;如: lsof -p 1681
三 cpu的排查
3.1. cpu的排查
1.获取使用cpu较高的进程id;使用ps命令或者top命令找到具体的使用较高的进程号
=》这里使用命令: top
2.使用消耗较高的线程id:用top -H -p pid来找到cpu使用率比较高的一些线程;执行top -H -p pid,这个命令就能显示刚刚找到的进程的所有线程的资源消耗情况。注意此时显示pid是线程的id。
=》这里使用命令: top -H -p 1
3.使用printf 命令将线程id转成16进制的nid,目的是在线程的堆栈信息中查找对应线程的信息。【因为线程堆栈中的nid是PID的16进制表示】
这里使用 【 printf '%x\n' pid】
得到nid (这里的pid为线程id),pid的16进制号为nid; 如下图:
=》这里使用命令: printf '%x\n' 66 ==》 0x42
3.2 分析阶段
3.2.1 使用jstack分析堆栈信息
1.使用jstack 命令查看具体线程堆栈信息: jstack pid |grep 'nid' -C5 –color
=》这里使用命令: jstack 1 | grep ‘ox42’ -C5 -color
查看线程堆栈:【进程id为1,线程id为66,的16进制表示为0x42】
3.2.1.1 案例1: 查看是否有waiting和timed_waiting等信息
1.通过从上文:抓取到了找到了nid为0x42的堆栈信,我们更关注是WAITING和TIMED_WAITING的部分,BLOCKED 等关键信息。
2.可以通过命令:【cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c】
来对jstack的状态有一个整体的把握,如果WAITING之类的特别多,那么多半是有问题啦。
3. 某个线程由于某种原因而进入WAITING状态,此时该功能整体不可用,但是无法复现;
每隔30s,jstack查询一下,对比一直停留在parking 导致的WAITING状态的线程。例如CountDownLatch倒计时器,使得相关线程等待->AQS->LockSupport.park()。
4.随机出现大量线程访问接口缓慢:出现是比较随机的;平时消耗的CPU不多,而且占用的内存也不高。 思路: 首先找到该接口,通过压测工具不断加大访问力度,大量线程将阻塞于该阻塞点。找到业务代码阻塞点,这里业务代码使用了TimeUnit.sleep()方法,使线程进入了TIMED_WAITING(期限等待)状态。
3.2.1.2 案例2: 查看是否有“VM Thread” 虚拟机GC回收线程
如果"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable,第一个双引号圈起来的就是线程名,如果是“VM Thread”这就是虚拟机GC回收线程了
3.2.1.3 案例3: 查看是否有“deadlock”关键字的信息
如果文件中找出关键字:deadlock;说明程序存在死锁,由于锁使用不当,导致死锁。
3.2.2 gc问题排查*
3.2.2.1 案例1:gc是否太频繁
1.使用jstat -gcutil 进程号 统计间隔毫秒 统计次数(缺省代表一致统计),查看某进程GC持续变化情况,如果发现返回中FGC(老年代垃圾回收次数)很大且一直增大。说明Full GC次数非常多,并且次数在不断增加,可以确认是Full GC。
接下来找到具体原因:
1)如果是生成大量的对象,导致内存溢出,执行jmap -dump:format=b,file=filename 进程ID,导出某进程下内存heap输出到文件中。可以通过eclipse的mat工具查看内存中有哪些对象比较多,查看具体内存对象占用情况。
2)内存占用不高,但是Full GC次数还是比较多,此时可能是代码中手动调用 System.gc()导致GC次数过多,这可以通过添加 -XX:+DisableExplicitGC来禁用JVM对显示GC的响应。
2. 使用jstat -gc pid 1000命令来对gc分代变化情况进行观察。 确定下gc是不是太频繁
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
JVM之jstat命令_s0c s1c s0u s1u ec eu oc ou mc mu ccsc ccsu ygc yg-CSDN博客
1000表示采样间隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。YGC/YGT、FGC/FGCT、GCT则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。
3.2.2.2 案例2:jmap -heap 是否存在堆内存要溢出
可以使用jmap -heap 进程ID查看一下进程的堆内从是不是要溢出了,特别是老年代内从使用情况一般是达到阈值(具体看垃圾回收器和启动时配置的阈值)就会进程Full GC。
3.2.2.3 案例3:使用jstat 查看内存使用率
(1)查看1154进程的gc状态,每2秒刷新一次:jstat -gcutil 1154 2000
(2)查看每次gc后内存使用率有没有降下来,有两种情况。
Case1:如果降下来了,但是很快又升上去了,说明有可能是QPS比较高,这时候需要扩容
Case2:如果没降下来,说明很有可能是内存泄漏导致的。
(3)针对case2:
先用jmap命令看一下内存中对象的占用大小(jmap -histo:live pid),看能不能直接发现问题所在。
如果不能,就把dump文件下载下来,用分析软件进行分析(比如eclipse memory analyzer)。
(4)dump文件下载下来后,就要先让线上服务器可用,如果是刚上线了版本,就把服务器进行回滚;如果是很长时间没发版本,现在出现了问题,就重启服务
https://blog.csdn.net/xnninger/article/details/111499725
3.2.3 上下文切换:cs(context switch)一列则代表了上下文切换的次数
1.我们可以使用vmstat命令来进行查看, 频繁上下文问题,
2. 对特定的pid进行监控那么可以使用 pidstat -w pid命令 ,cswch和nvcswch表示自愿及非自愿切换。
四 内存的排查
4.1 内存整体排查
1.先用free命令先来检查一发内存的各种情况
4.2 堆内内存
内存问题大多还都是堆内内存问题。表象上主要分为OOM和StackOverflow。
4.2.1 OOM异常
4.2.2.1 案例1:java栈问题
Jvm的内存不足时报:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
原因:java栈的内存不足;
解决策略:
1.先从代码层面排查问题,如线程池忘记shutdown动作。
我们一般先会看下总体线程,通过pstreee -p pid |wc -l。
2.使用jstack或者jmap进行查看堆内存变化,
线程太多而且不被及时gc会引发报unable to create new native thread;
如果一切正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。
3.同时可以在【/etc/security/limits.conf】文件中,设置nofile(打开文件的最大数量)和nproc(用户可创建的进程的最大数量),来增大os对线程的限制。如下图:
4.2.2.2 案例2:Java heap space内存溢出
Jvm的内存不足时报:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
原因:这是最常见的OOM错误,原因是堆内存使用已经达到最大值-xmx。
解决策略:
1.先从代码层面排查问题
每次请求都new对象,导致大量重复创建对象;进行文件流操作后未正确关闭;手动不当触发gc;ByteBuffer缓存分配不合理等都会造成代码OOM。
2.通过jstack和jmap去定位问题,使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常呀等。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。
4.2.2.3 案例3:Meta space 元空间问题
Jvm的内存不足时报:
Caused by: java.lang.OutOfMemoryError: Meta space
原因:元数据区的内存使用已经达到XX:MaxMetaspaceSize设置的最大值。
解决策略:
1.先从代码层面排查问题
2.参数方面可以通过XX:MaxPermSize来进行调整(这里就不说1.8以前的永久代了)。
4.2.2.4 案例4:statckoverflow异常
栈内存溢出:
Exception in thread "main" java.lang.StackOverflowError
原因:线程栈需要的内存大于Xss值。
解决策略:
1.先从代码层面排查问题
2.参数方面合理调整xss的大小,否则又报oom
4.2.2 排查工具-jmap定位代码内存泄漏
一般使用JMAPjmap -dump:format=b,file=filename pid来导出dump文件
使用Eclipse的Mat工具对dump文件进行分析:
1.内存泄漏问题一般我们直接选Leak Suspects即可,mat给出了内存泄漏的建议。
2.选择Top Consumers来查看最大对象报告
3.和线程相关的问题可以选择thread overview进行分析
4.除此之外就是选择Histogram类概览来自己慢慢分析
5.在启动参数中指定-XX:+HeapDumpOnOutOfMemoryError来保存OOM时的dump文件。
4.3 堆外内存(了解)
Java虚拟机的堆以外的内存叫堆外内存(DirectBuffer),对于整个机器内存而言,除堆内内存以外部分即为堆外内存。堆外内存溢出表现就是物理常驻内存增长快。
4.3.1 根据进程查看内存占有情况
1.堆外内存溢出往往是和NIO的使用相关,一般我们先通过pmap来查看下进程占用的内存情况pmap -x pid | sort -rn -k3 | head -30,这段意思是查看对应pid倒序前30大的内存段。这边可以再过一段时间后再跑一次命令看看内存增长情况,或者和正常机器比较可疑的内存段在哪里。
我们如果确定有可疑的内存端,需要通过gdb来分析gdb --batch --pid {pid} -ex "dump memory filename.dump {内存起始地址} {内存起始地址+内存块大小}"
4.3.2 增长缓慢情况
1.一般对于堆外内存缓慢增长直到爆炸的情况来说,可以先设一个基线jcmd pid VM.native_memory baseline。
2.然后等放一段时间后再去看看内存增长的情况,通过jcmd pid VM.native_memory detail.diff(summary.diff)做一下summary或者detail级别的diff。
通过jcmd命令分析后,可以看到:堆内、线程以及gc等信息,
这里关注Internal的内存增长,如果增长十分明显的话那就是有问题了。
3.此外在系统层面,我们还可以使用strace命令来监控内存分配 strace -f -e "brk,mmap,munmap" -p pid
4.3.3 对外内存对象的使用情况
1.跟踪一下DirectByteBuffer对象的内存情况,通过jmap -histo:live pid手动触发fullGC来看看堆外内存有没有被回收。如果被回收了,那么大概率是堆外内存本身分配的太小了,通过-XX:MaxDirectMemorySize进行调整。如果没有什么变化,那就要使用jmap去分析那些不能被gc的对象,以及和DirectByteBuffer之间的引用关系了。
4.4 GC问题
使用jstat来获取当前GC分代变化信息。而更多时候,我们是通过GC日志来排查问题的,在启动参数中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps来开启GC日志。
通过GC日志大致推断出youngGC与fullGC是否过于频繁或者耗时过长,从而对症下药。
这里选用G1垃圾收集器来做分析,这边也建议大家使用G1-XX:+UseG1GC
4.4.1 youngGC过于频繁
1.youngGC频繁一般是短周期小对象较多。
A)是不是Eden区/新生代设置的太小了,
B)看能否通过调整-Xmn、-XX:SurvivorRatio等参数设置来解决问题。
C)如果参数正常,但是young gc频率还是太高,就需要使用Jmap和MAT对dump文件进行进一步排查了。
4.4.2 youngGC耗时过长
查看日志:确认gc耗时在那一块。以G1日志为例,可以关注Root Scanning、Object Copy、Ref Proc等阶段。
Ref Proc耗时长,就要注意引用相关的对象。
Root Scanning耗时长,就要注意线程数、跨代引用。Object Copy则需要关注对象生存周期。
比如说图中的Root Scanning和正常时间段,进行对比增长较多,那就是起的线程太多了。
4.3.3 触发Full Gc
fullGC的原因可能包括以下这些,以及参数调整方面的一些思路:
1.程序主动执行System.gc():不要随便写就对了。
2.大对象分配失败:大对象找不到合适的region空间进行分配,就会进行fullGC,这种情况下可以增大内存或者增大-XX:G1HeapRegionSize。
3.晋升失败:在GC的时候没有足够的内存供存活/晋升对象使用,所以触发了Full GC。这时候可以通过-XX:G1ReservePercent来增加预留内存百分比,减少-XX:InitiatingHeapOccupancyPercent来提前启动标记,-XX:ConcGCThreads来增加标记线程数也是可以的。
4.并发阶段失败:在并发标记阶段,MixGC之前老年代就被填满了,那么这时候G1就会放弃标记周期。这种情况,可能就需要增加堆大小,或者调整并发标记线程数-XX:ConcGCThreads。
5.在启动参数中配置【-XX:HeapDumpPath=/xxx/dump.hprof】 来指定导出的dump文件存储;通过使用jinfo来进行gc前后的变化。
五 网络的排查
5.1 超时问题
读写超时。readTimeout/writeTimeout;
连接超时。connectionTimeout
5.2 TCP队列溢出
执行netstat -s | egrep "listen|LISTEN" ,快速定位到tcp队列溢出
overflowed表示全连接队列溢出的次数,sockets dropped表示半连接队列溢出的次数。
5.3 close_wait
close_wait问题往往是由于某个地方阻塞住了,没有正常关闭连接,从而渐渐地消耗完所有的线程。想要定位这类问题,最好是通过jstack来分析线程堆栈来排查问题.
例如:开发同学说应用上线后CLOSE_WAIT就一直增多,直到挂掉为止,jstack后找到比较可疑的堆栈,是大部分线程都卡在了countdownlatch.await方法。发现异常仅仅是最简单的升级sdk后常出现的class not found。
JAVA 线上故障排查完整套路,从 CPU、磁盘、内存、网络、GC 一条龙!
5.4 RST包问题
使用tcpdump命令进行抓包,并使用wireshark进行简单分析了。
1. tcpdump -i en0 tcp -w xxx.cap,en0表示监听的网卡。
接下来我们通过wireshark打开抓到的包,可能就能看到如下图所示,红色的就表示RST包了。