JAVA线上排查过程总体思路
线上故障包括:cpu、磁盘、内存、网络问题。通常df、free、top后一次进行jstack和jmap
CPU
cpu问题的出现一般和业务逻辑(死循环)、频繁GC以及上下文切换有关,可以使用jstack来分析对战情况
jstack分析cpu问题
使用top命令看总体进程运行情况。 接着top —H -p -pid查找cpu使用率较高的线程 pid转成16进制的nid,然后jstack pid|grep ’nid‘ -C5 -color
当然更常见的是我们对整个jstack文件进行分析,通常我们会比较关注WAITING和TIMED_WAITING的部分,BLOCKED就不用说了。我们可以使用命令cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c来对jstack的状态有一个整体的把握,如果WAITING之类的特别多,那么多半是有问题啦。
频繁GC
然我们还是会使用jstack来分析问题,但有时候我们可以先确定下gc是不是太频繁,使用jstat -gc pid 1000命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。YGC/YGT、FGC/FGCT、GCT则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析
### 上下文切换
VMstat命令,cs(context switch)一列则代表了上下文切换的次数
如果我们希望对特定的pid进行监控那么可以使用 pidstat -w pid命令,cswch和nvcswch表示自愿及非自愿切换。
磁盘
通过iostatiostat -d -k -x来进行分析。最后一列%util可以看到每块磁盘写入的程度,而rrqpm/s以及wrqm/s分别表示读写速度,一般就能帮助定位到具体哪块磁盘出现问题了
我们要转换成pid,可以通过readlink来找到pid
找到pid之后就可以看这个进程具体的读写情况cat /proc/pid/io
内存
我们会先用free命令先来检查一发内存的各种情况
OOM
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改/etc/security/limits.confnofile和nproc来增大os对线程的限制
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。
Caused by: java.lang.OutOfMemoryError: Meta space
参数方面可以通过XX:MaxPermSize来进行调整
Stack Overflow
表示线程栈需要的内存大于Xss值,但调整的太大可能又会引起OOM。
JMAP定位代码内存泄漏
JMAPjmap -dump:format=b,file=filename pid来导出dump文件
通过mat(Eclipse Memory Analysis Tools)导入dump文件进行分析,内存泄漏问题一般我们直接选Leak Suspects即可,mat给出了内存泄漏的建议。另外也可以选择Top Consumers来查看最大对象报告。
查看线程情况
总体线程,通过pstreee -p pid |wc -l。或者直接通过查看/proc/pid/task的数量即为线程数量
堆外内存
通过pmap来查看下进程占用的内存情况pmap -x pid | sort -rn -k3 | head -30,这段意思是查看对应pid倒序前30大的内存段
我们如果确定有可疑的内存端,需要通过gdb来分析gdb --batch --pid {pid} -ex "dump memory filename.dump {内存起始地址} {内存起始地址+内存块大小}"
一般对于堆外内存缓慢增长直到爆炸的情况来说,可以先设一个基线jcmd pid VM.native_memory baseline。然后等放一段时间后再去看看内存增长的情况,通过jcmd pid VM.native_memory detail.diff(summary.diff)做一下summary或者detail级别的diff
大招:通过jmap -histo:live pid手动触发fullGC来看看堆外内存有没有被回收。如果被回收了,那么大概率是堆外内存本身分配的太小了,通过-XX:MaxDirectMemorySize进行调整
GC问题
GC问题不只是和内存问题相关,还有可能引起CPU负载、网络问题等系列并发症,只是相对来说和内存联系紧密些
通过GC日志来排查问题的,在启动参数中加上-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps来开启GC日志
youngGC频繁一般是短周期小对象较多,先考虑是不是Eden区/新生代设置的太小了
fullGC的原因可能包括以下这些,以及参数调整方面的一些思路:
-
并发阶段失败:在并发标记阶段,MixGC之前老年代就被填满了,那么这时候G1就会放弃标记周期。这种情况,可能就需要增加堆大小,或者调整并发标记线程数
-XX:ConcGCThreads。 -
晋升失败:在GC的时候没有足够的内存供存活/晋升对象使用,所以触发了Full GC。这时候可以通过
-XX:G1ReservePercent来增加预留内存百分比,减少-XX:InitiatingHeapOccupancyPercent来提前启动标记,-XX:ConcGCThreads来增加标记线程数也是可以的。 -
大对象分配失败:大对象找不到合适的region空间进行分配,就会进行fullGC,这种情况下可以增大内存或者增大
-XX:G1HeapRegionSize。
们可以在启动参数中配置-XX:HeapDumpPath=/xxx/dump.hprof来dump fullGC相关的文件,并通过jinfo来进行gc前后的dump
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
对比后主要关注被gc掉的问题对象来定位问题。
网络
两个队列:syns queue(半连接队列)、accept queue(全连接队列)。三次握手,在server收到client的syn后,把消息放到syns queue,回复syn+ack给client,server收到client的ack,如果这时accept queue没满,那就从syns queue拿出暂存的信息放入accept queue中,否则按tcp_abort_on_overflow指示的执行。
netstat命令,执行netstat -s | egrep "listen|LISTEN"
overflowed表示全连接队列溢出的次数,sockets dropped表示半连接队列溢出的次数。
在实际开发中,我们往往会看到connection reset / connection reset by peer错误,这种情况就是RST包导致的
如果像不存在的端口发出建立连接SYN请求,那么服务端发现自己并没有这个端口则会直接返回一个RST报文,用于中断连接
一方机器由于网络实在太差TCP报文失踪了,另一方关闭了该连接,然后过了许久收到了之前失踪的TCP报文,但由于对应的TCP连接已不存在,那么会直接发一个RST包以便开启新的连接。
一方长期未收到另一方的确认报文,在一定时间或重传次数后发出RST报文
tcpdump命令进行抓包,并使用wireshark进行简单分析了。tcpdump -i en0 tcp -w xxx.cap,en0表示监听的网卡。接下来我们通过wireshark打开抓到的包,可能就能看到如下图所示,红色的就表示RST包了。
ss命令查看time_wait和closed_wait会更快ss -ant | awk '{++S[$1]} END {for(a in S) print a, S[a]}'