1. 程式人生 > >Java內存泄漏分析系列之一:使用jstack定位線程堆棧信息

Java內存泄漏分析系列之一:使用jstack定位線程堆棧信息

技術分享 對象 rgs 沒有 參數 導致 dump 單獨 src

原文地址:http://www.javatang.com

前一段時間上線的系統升級之後,出現了嚴重的高CPU的問題,於是開始了一系列的優化處理之中,現在將這個過程做成一個系列的文章。

基本概念

在對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內存泄漏分析系列之一:使用jstack定位線程堆棧信息