一整套线上故障排查技巧,爱了( 三 )


④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 之间的引用关系了 。
GC 问题
堆内内存泄漏总是和 GC 异常相伴 。 不过 GC 问题不只是和内存问题相关 , 还有可能引起 CPU 负载、网络问题等系列并发症 , 只是相对来说和内存联系紧密些 , 所以我们在此单独总结一下 GC 相关问题 。
我们在 CPU 章介绍了使用 jstat 来获取当前 GC 分代变化信息 。
而更多时候 , 我们是通过 GC 日志来排查问题的 , 在启动参数中加上 -verbose:gc , -XX:+PrintGCDetails , -XX:+PrintGCDateStamps , -XX:+PrintGCTimeStamps 来开启 GC 日志 。