一整套Java线上故障排查技巧,爱了!

线上故障主要会包括 CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。

同时例如 jstack、jmap 等工具也是不囿于一个方面的问题的,基本上出问题就是 df、free、top 三连,然后依次 jstack、jmap 伺候,具体问题具体分析即可。

CPU

一般来讲我们首先会排查 CPU 方面的问题。CPU 异常往往还是比较好定位的。
原因包括业务逻辑问题(死循环)、频繁 GC 以及上下文切换过多。

而最常见的往往是业务逻辑(或者框架逻辑)导致的,可以使用 jstack 来分析对应的堆栈情况。

①使用 jstack 分析 CPU 问题

我们先用 ps 命令找到对应进程的 pid(如果你有好几个目标进程,可以先用 top 看一下哪个占用比较高)。

接着用top -H -p pid来找到 CPU 使用率比较高的一些线程:在这里插入图片描述
然后将占用最高的 pid 转换为 16 进制 printf '%x\n' pid 得到 nid:在这里插入图片描述
接着直接在 jstack 中找到相应的堆栈信息jstack pid |grep 'nid' -C5 –color在这里插入图片描述

可以看到我们已经找到了 nid 为 0x42 的堆栈信息,接着只要仔细分析一番即可。

当然更常见的是我们对整个 jstack 文件进行分析,通常我们会比较关注 WAITINGTIMED_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 方面做进一步分析,具体可以参考一下 GC章节的描述。在这里插入图片描述

③上下文切换

针对频繁上下文问题,我们可以使用 vmstat 命令来进行查看:在这里插入图片描述
cs(context switch)一列则代表了上下文切换的次数。如果我们希望对特定的 pid 进行监控那么可以使用 pidstat -w pid 命令,cswch 和 nvcswch 表示自愿及非自愿切换。在这里插入图片描述


磁盘

磁盘问题和 CPU 一样是属于比较基础的。首先是磁盘空间方面,我们直接使用 df -hl 来查看文件系统状态:
在这里插入图片描述

更多时候,磁盘问题还是性能上的问题。我们可以通过 iostatiostat -d -k -x来进行分析:
在这里插入图片描述

最后一列 %util 可以看到每块磁盘写入的程度,而 rrqpm/s 以及 wrqm/s 分别表示读写速度,一般就能帮助定位到具体哪块磁盘出现问题了。

另外我们还需要知道是哪个进程在进行读写,一般来说开发自己心里有数,或者用 iotop 命令来进行定位文件读写的来源。在这里插入图片描述

不过这边拿到的是 tid,我们要转换成 pid,可以通过readlink来找到 pidreadlink -f /proc/*/task/tid/…/…。在这里插入图片描述

找到 pid 之后就可以看这个进程具体的读写情况 cat /proc/pid/io在这里插入图片描述
我们还可以通过 lsof 命令来确定具体的文件读写情况lsof -p pid
在这里插入图片描述


内存

内存问题排查起来相对比 CPU 麻烦一些,场景也比较多。主要包括 OOMGC 问题堆外内存

一般来讲,我们会先用 free 命令先来检查一发内存的各种情况:
在这里插入图片描述

堆内内存

内存问题大多还都是堆内内存问题。表象上主要分为 OOM 和 Stack Overflow。

①OOM

JMV 中的内存不足,OOM 大致可以分为以下几种:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

这个意思是没有足够的内存空间给线程分配 Java 栈,基本上还是线程池代码写的有问题,比如说忘记 shutdown,所以说应该首先从代码层面来寻找问题,使用jstack 或者jmap

如果一切都正常,JVM 方面可以通过指定 Xss 来减少单个 thread stack 的大小。

另外也可以在系统层面,可以通过修改/etc/security/limits.confnofilenproc 来增大 os 对线程的限制。在这里插入图片描述

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

这个意思是堆的内存占用已经达到 -Xmx 设置的最大值,应该是最常见的的 OOM 错误了。

解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过 jstack 和 jmap 去定位问题。如果说一切都正常,才需要通过调整 Xmx 的值来扩大内存。

Caused by: java.lang.OutOfMemoryError: Meta space

这个意思是元数据区的内存占用已经达到XX:MaxMetaspaceSize 设置的最大值,排查思路和上面的一致,参数方面可以通过 XX:MaxPermSize 来进行调整(这里就不说 1.8 以前的永久代了)。

②Stack Overflow

栈内存溢出,这个大家见到也比较多。

Exception in thread "main" java.lang.StackOverflowError

表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起 OOM。

③使用 JMAP 定位代码内存泄漏

上述关于 OOM 和 Stack Overflow 的代码排查方面,我们一般使用 JMAPjmap -dump:format=b,file=filename pid 来导出 dump 文件:在这里插入图片描述

通过 mat(Eclipse Memory Analysis Tools)导入 dump 文件进行分析,内存泄漏问题一般我们直接选 Leak Suspects 即可,mat 给出了内存泄漏的建议。

另外也可以选择 Top Consumers 来查看最大对象报告。和线程相关的问题可以选择 thread overview 进行分析。

除此之外就是选择 Histogram 类概览来自己慢慢分析,大家可以搜搜 mat 的相关教程。在这里插入图片描述
日常开发中,代码产生内存泄漏是比较常见的事,并且比较隐蔽,需要开发者更加关注细节。

  • 比如说每次请求都 new 对象,导致大量重复创建对象;
  • 进行文件流操作但未正确关闭;
  • 手动不当触发 GC;
  • ByteBuffer 缓存分配不合理等都会造成代码 OOM。

另一方面,我们可以在启动参数中指定-XX:+HeapDumpOnOutOfMemoryError来保存 OOM 时的 dump 文件。

④GC 问题和线程

GC 问题除了影响 CPU 也会影响内存,排查思路也是一致的。一般先使用 jstat 来查看分代变化情况,比如 youngGC 或者 FullGC 次数是不是太多呀;EU、OU 等指标增长是不是异常呀等。

线程的话太多而且不被及时 GC 也会引发 OOM,大部分就是之前说的 unable to create new native thread

除了 jstack 细细分析 dump 文件外,我们一般先会看下总体线程,通过pstreee -p pid |wc -l在这里插入图片描述
或者直接通过查看 /proc/pid/task 的数量即为线程数量。在这里插入图片描述

堆外内存

如果碰到堆外内存溢出,那可真是太不幸了。首先堆外内存溢出表现就是物理常驻内存增长快,报错的话视使用方式都不确定。

如果由于使用 Netty 导致的,那错误日志里可能会出现 OutOfDirectMemoryError 错误,如果直接是 DirectByteBuffer,那会报 OutOfMemoryError: Direct buffer memory

堆外内存溢出往往是和 NIO 的使用相关,一般我们先通过 pmap 来查看下进程占用的内存情况 pmap -x pid | sort -rn -k3 | head -30,这段意思是查看对应 pid 倒序前 30 大的内存段。

这边可以再一段时间后再跑一次命令看看内存增长情况,或者和正常机器比较可疑的内存段在哪里。在这里插入图片描述

我们如果确定有可疑的内存端,需要通过 gdb 来分析

gdb --batch --pid {pid} -ex "dump memory filename.dump {内存起始地址} {内存起始地址+内存块大小}"

在这里插入图片描述

获取 dump 文件后可用heaxdump进行查看 hexdump -C filename | less,不过大多数看到的都是二进制乱码。

NMT 是 Java7U40 引入的 HotSpot 新特性,配合 jcmd命令我们就可以看到具体内存组成了。

需要在启动参数中加入 -XX:NativeMemoryTracking=summary 或者 -XX:NativeMemoryTracking=detail,会有略微性能损耗。

一般对于堆外内存缓慢增长直到爆炸的情况来说,可以先设一个基线jcmd pid VM.native_memory baseline在这里插入图片描述
然后等放一段时间后再去看看内存增长的情况,通过jcmd pid VM.native_memory detail.diff(summary.diff)做一下 summary 或者 detail 级别的 diff。
在这里插入图片描述
在这里插入图片描述
可以看到jcmd 分析出来的内存十分详细,包括堆内、线程以及 GC(所以上述其他内存异常其实都可以用 nmt 来分析),这边堆外内存我们重点关注 Internal 的内存增长,如果增长十分明显的话那就是有问题了。

detail 级别的话还会有具体内存段的增长情况,如下图:在这里插入图片描述
此外在系统层面,我们还可以使用 strace 命令来监控内存分配 strace -f -e “brk,mmap,munmap” -p pid。

这边内存分配信息主要包括了 pid 和内存地址:在这里插入图片描述
不过其实上面那些操作也很难定位到具体的问题点,关键还是要看错误日志栈,找到可疑的对象,搞清楚它的回收机制,然后去分析对应的对象。

比如 DirectByteBuffer 分配内存的话,是需要 Full GC 或者手动 system.gc 来进行回收的(所以最好不要使用-XX:+DisableExplicitGC)。

那么其实我们可以跟踪一下 DirectByteBuffer 对象的内存情况,通过 jmap -histo:live pid 手动触发 Full GC 来看看堆外内存有没有被回收。

如果被回收了,那么大概率是堆外内存本身分配的太小了,通过 -XX:MaxDirectMemorySize 进行调整。

如果没有什么变化,那就要使用 jmap 去分析那些不能被 GC 的对象,以及和 DirectByteBuffer 之间的引用关系了。

原文地址

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值