1. 程式人生 > >分析系列之一:使用jstack定位執行緒堆疊資訊

分析系列之一:使用jstack定位執行緒堆疊資訊

基本概念

在對Java記憶體洩漏進行分析的時候,需要對jvm執行期間的記憶體佔用、執行緒執行等情況進行記錄的dump檔案,常用的主要有thread dump和heap dump。

  • thread dump 主要記錄JVM在某一時刻各個執行緒執行的情況,以棧的形式顯示,是一個文字檔案。通過對thread dump檔案可以分析出程式的問題出現在什麼地方,從而定位具體的程式碼然後進行修正。thread dump需要結合佔用系統資源的執行緒id進行分析才有意義。
  • heap dump 主要記錄了在某一時刻JVM堆中物件使用的情況,即某個時刻JVM堆的快照,是一個二進位制檔案,主要用於分析哪些物件佔用了太對的堆空間,從而發現導致記憶體洩漏的物件。

上面兩種dump檔案都具有實時性,因此需要在伺服器出現問題的時候生成,並且多生成幾個檔案,方便進行對比分析。下面我們先來說一下如何生成 thread dump。

使用jstack生成thread dump

當伺服器出現高CPU的時候,首先執行 top -c 命令動態顯示程序及佔用資源的排行,如下圖:

top後面的引數-c可以顯示程序詳細的資訊。top命令執行的時候還可以執行一些快捷鍵:

  • 1 對於多核伺服器,可以顯示各個CPU佔用資源的情況
  • shift+h 顯示所有的執行緒資訊
  • shift+w 將當前 top 命令的設定儲存到 ~/.toprc 檔案中,這樣不用每次都執行快捷鍵了

以上圖為例,pid為1503的程序佔用了大量的CPU資源,接下來需要將佔用CPU最高程序中的執行緒打印出來,可以用 top -bn1 -H -p <pid> 命令,執行結果如下:

上面 -bn1 引數的含義是隻輸出一次結果,而不是顯示一個動態的結果。

我個人請喜歡用 ps -mp <pid> -o THREAD,tid,time | sort -k2r 命令檢視,後面的sort引數根據執行緒佔用的cpu比例進行排序,結果如下:

接下來我們清楚今天的主角 jstack,這是一個在JDK5開始提供的內建工具,可以列印指定程序中執行緒執行的狀態,包括執行緒數量、是否存在死鎖、資源競爭情況和執行緒的狀態等等。有下面的幾個常用的引數:

  • -l 長列表,列印關於鎖的附加資訊
  • -m 列印java和jni框架的所有棧資訊

因為thread id在棧資訊中是以十六進位制的形式顯示的,因此需要使用 printf "%x \n" <tid> 命令將現場id轉成十六進位制的值,然後執行 jstack -l <pid> | grep <thread-hex-id> -A 10 命令顯示出錯的堆疊資訊,如下圖:

上面命令中 -A 10 引數用來指定顯示行數,否則只會顯示一行資訊。

這樣通過上圖,可以很快地定位到程式問題的程式碼,然後對程式碼進行分析和改進即可。注意:需要在多個時間段提出多個 Thread Dump資訊,然後綜合進行對比分析,單獨分析一個檔案是沒有意義的。

生成shell檔案

上面講述了整個的分析過程,不過所有的命令就是實時的,所以最好建立一個shell指令碼瞬間執行完成,下面對 當CPU飆高時,它在做什麼 這篇文章中所提供的shell進行了改進如下:

#!/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生成的檔案進行分析

參考資料:
java程式效能分析之thread dump和heap dump
JVM調優之jstack找出最耗cpu的執行緒並定位程式碼
當CPU飆高時,它在做什麼
JAVA應用CPU佔用100%|記憶體洩漏分析總結