原文地址:Java內(nèi)存泄漏分析系列之一:使用jstack定位線程堆棧信息
前一段時間上線的系統(tǒng)升級之后尚卫,出現(xiàn)了嚴(yán)重的高CPU的問題,于是開始了一系列的優(yōu)化處理之中,現(xiàn)在將這個過程做成一個系列的文章。
基本概念
在對Java內(nèi)存泄漏進(jìn)行分析的時候窗看,需要對jvm運(yùn)行期間的內(nèi)存占用茸歧、線程執(zhí)行等情況進(jìn)行記錄的dump文件倦炒,常用的主要有thread dump和heap dump。
- thread dump 主要記錄JVM在某一時刻各個線程執(zhí)行的情況软瞎,以棧的形式顯示逢唤,是一個文本文件。通過對thread dump文件可以分析出程序的問題出現(xiàn)在什么地方涤浇,從而定位具體的代碼然后進(jìn)行修正鳖藕。thread dump需要結(jié)合占用系統(tǒng)資源的線程id進(jìn)行分析才有意義。
- heap dump 主要記錄了在某一時刻JVM堆中對象使用的情況只锭,即某個時刻JVM堆的快照著恩,是一個二進(jìn)制文件,主要用于分析哪些對象占用了太對的堆空間,從而發(fā)現(xiàn)導(dǎo)致內(nèi)存泄漏的對象喉誊。
上面兩種dump文件都具有實時性邀摆,因此需要在服務(wù)器出現(xiàn)問題的時候生成,并且多生成幾個文件伍茄,方便進(jìn)行對比分析栋盹。下面我們先來說一下如何生成 thread dump。
使用jstack生成thread dump
當(dāng)服務(wù)器出現(xiàn)高CPU的時候敷矫,首先執(zhí)行 top -c
命令動態(tài)顯示進(jìn)程及占用資源的排行例获,如下圖:
top后面的參數(shù)
-c
可以顯示進(jìn)程詳細(xì)的信息。top
命令執(zhí)行的時候還可以執(zhí)行一些快捷鍵:
-
1
對于多核服務(wù)器曹仗,可以顯示各個CPU占用資源的情況 -
shift+h
顯示所有的線程信息 -
shift+w
將當(dāng)前top
命令的設(shè)置保存到~/.toprc
文件中榨汤,這樣不用每次都執(zhí)行快捷鍵了
以上圖為例,pid為1503的進(jìn)程占用了大量的CPU資源怎茫,接下來需要將占用CPU最高進(jìn)程中的線程打印出來件余,可以用 top -bn1 -H -p <pid>
命令,執(zhí)行結(jié)果如下:
上面
-bn1
參數(shù)的含義是只輸出一次結(jié)果遭居,而不是顯示一個動態(tài)的結(jié)果啼器。
我個人請喜歡用 ps -mp <pid> -o THREAD,tid,time | sort -k2r
命令查看,后面的sort參數(shù)根據(jù)線程占用的cpu比例進(jìn)行排序俱萍,結(jié)果如下:
接下來我們清楚今天的主角 jstack
端壳,這是一個在JDK5開始提供的內(nèi)置工具,可以打印指定進(jìn)程中線程運(yùn)行的狀態(tài)枪蘑,包括線程數(shù)量损谦、是否存在死鎖、資源競爭情況和線程的狀態(tài)等等岳颇。有下面的幾個常用的參數(shù):
-
-l
長列表照捡,打印關(guān)于鎖的附加信息 -
-m
打印java和jni框架的所有棧信息
因為thread id在棧信息中是以十六進(jìn)制的形式顯示的,因此需要使用 printf "%x \n" <tid>
命令將現(xiàn)場id轉(zhuǎn)成十六進(jìn)制的值话侧,然后執(zhí)行 jstack -l <pid> | grep <thread-hex-id> -A 10
命令顯示出錯的堆棧信息栗精,如下圖:
上面命令中
-A 10
參數(shù)用來指定顯示行數(shù),否則只會顯示一行信息瞻鹏。
這樣通過上圖悲立,可以很快地定位到程序問題的代碼,然后對代碼進(jìn)行分析和改進(jìn)即可新博。
生成shell文件
上面講述了整個的分析過程薪夕,不過所有的命令就是實時的,所以最好創(chuàng)建一個shell腳本瞬間執(zhí)行完成赫悄,下面對 當(dāng)CPU飆高時原献,它在做什么 這篇文章中所提供的shell進(jìn)行了改進(jìn)如下:
#!/bin/bash
if [ $# -ne 1 ]; 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
# checking pid
if test -z "$($JAVA_HOME/bin/jps -l | cut -d '' -f 1 | grep $pid)"
then
echo "process of $pid is not exists"
exit
fi
#line number
if test -z $linenum
then
linenum=10
fi
stackfile=stack$pid.dump
threadsfile=threads$pid.dump
# generate java stack
$JAVA_HOME/bin/jstack -l $pid >> $stackfile
ps -mp $pid -o THREAD,tid,time | sort -k2r | awk '{if ($1 !="USER" && $2 != "0.0" && $8 !="-") print $8;}' | xargs printf "%x\n" >> $threadsfile
tids="$(cat $threadsfile)"
for tid in $tids
do
echo "------------------------------ ThreadId ($tid) ------------------------------"
cat $stackfile | grep 0x$tid -A $linenum
done
rm -f $stackfile $threadsfile
下一篇文章將要講述如何對jstack生成的文件進(jìn)行分析
參考資料:
java程序性能分析之thread dump和heap dump
JVM調(diào)優(yōu)之jstack找出最耗cpu的線程并定位代碼
當(dāng)CPU飆高時馏慨,它在做什么
JAVA應(yīng)用CPU占用100%|內(nèi)存泄漏分析總結(jié)