简述

测试环境压测发现cpu飙升,cpu升高通常情况表示代码中有大量计算逻辑/死循环,内存泄漏引起的频繁GC也可导致。因此,这类问题可以从以上几个方面排查。

dump基本概念

在问题定位和性能分析过程中,dump文件有很大帮助。dump文件记录了jvm运行期间内存状态,线程执行执行情况,常用的有thread dump,heap dump。简单来说,thread dump是记录线程执行信息,heap dump是记录jvm内存信息。

thread dump : 主要记录JVM在某一时刻各个线程执行的情况,以栈的形式显示,是一个文本文件。通过对thread dump文件可以分析出程序的问题出现在什么地方,从而定位具体的代码然后进行修正。thread dump需要结合占用系统资源的线程id进行分析才有意义。

heap dump : 主要记录了在某一时刻JVM堆中对象使用的情况,即某个时刻JVM堆的快照,是一个二进制文件,主要用于分析哪些对象占用了太对的堆空间,从而发现导致内存泄漏的对象。

dump文件具有实时性,因此需要再服务出现问题时候生成。并且多生成几个文件进行对比分析~(后面有thread dump生成脚本)

cpu问题排查:

(1)top 命令查看占用cpu较高线程,得到pid;

(2)ps -mp pid -o THREAD,tid,time | sort -nr -k 2  根据pid,查看占用cpu较高的tid

(3)printf "%x\n" tid  将tid转化为16进制(thread dump中以十六进制显示线程信息)

(4)jstack pid | grep tid -A 30  查看线程执行情况

通过查看线程执行信息一般即可定位到问题,如果占用cpu过高的线程为GC线程,这时就需要考虑内存泄漏引起的cpu升高问题(需要观察gc状态,及jmap生成heap dump文件,通过MAT/jvisualvm分析内存占用大对象。ref: , )

如果占用cpu的线程不是gc线程,查看线程信息需要关注线程是否正在执行,未执行的线程通常不会引起cpu升高。由于thread dump信息具有实时性,上一时刻占用cpu高的线程id,在查看线程信息时可能已经执行完毕,已经不是占用cpu高的线程,因此可通过脚本实时监控监控线程状态。(下面脚本可实时打印占用cpu过高线程执行信息,


#!/bin/bash
# 显示占用cpu较高java线程信息
# 入参 1. pid 必传
#      2. linenum  每个线程显示信息条数 非必传(默认10)


if [ $# -le 0 ]; then
    echo "usage: $0 <pid> [line-number]"
    exit 1
fi

# java home
if test -z $JAVA_HOME; then
    JAVA_HOME="/usr/local/jdk"
fi

# pid
pid=$1
if test -z $pid; then
    echo "pid can not be null"
    exit
else
    echo "checking pid($pid)"
fi

#check pid
if test -z "$($JAVA_HOME/bin/jps -l | cut -d ' ' -f 1 | grep $pid)"; then
    echo "process $pid not exist"
    exit
fi

#line num
line_num=$2
if test -z $line_num; then
    line_num=10
fi

# generate stack & thread file
stackfile=stack$pid.dump
threadsfile=thread$pid.dump
$JAVA_HOME/bin/jstack -l $pid >> $stackfile
ps -mp $pid -o THREAD,tid,time | sort -nr -k 2 | awk '{if ($1!="USER" && $2!="0.0" && $8!="-") print $8;}' | xargs printf "%x\n" >> $threadsfile
tids="$(cat $threadsfile)"
for tid in $tids; do
    echo "-------------thread($pid)----------------"
    cat $stackfile | grep $tid -A $line_num
done

rm -rf $stackfile
rm -rf $threadsfile