Table of Contents
/proc/<pid>/smaps
プロセスメモリマッピングファイルからjavaスレッドのスタック使用状
況をいい感じに出力するスクリプトを作りました。
1 環境
- java version “1.8.0_45”
- x86_64 gnu/linux
/proc/<pid>/smaps にスタックセグメントが次のように
[stack:スレッドID]
ラベルが出 力されていること(Linux 3.4 以降)7f3984233000-7f3984331000 rw-p 00000000 00:00 0 [stack:2988] Size: 1016 kB Rss: 92 kB Pss: 92 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 92 kB Referenced: 92 kB Anonymous: 92 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: rd wr mr mw me ac
2 スクリプト
スクリプトの元ネタは以前の記事を参考してください。 jvmのスタックサイズについて
#!/bin/sh ########################################################################### # jvm_stacksize.sh - take a jvm stack size snapshot # # Authors: Akira Wakana <jalen.cn@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses # # Usage: # $jvm_stacksize.sh <JVM ProcessID> ########################################################################### PID=$1 ps ${PID} | grep [j]ava > /dev/null || { echo "-----------------------------------------------------------------------" ps -ef | grep [j]ava echo "-----------------------------------------------------------------------" echo -n "please input the java process id: " read PID } printf "[ PID ]\t[StackSize(kB)]\t[GuardPage(kB)]\t[UsedSize(kB)]\t[Thread Name]\n" # jstackの出力結果からスレッドIDと名前を抽出する jstack ${PID} | grep nid | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/" | sort | while read line do # スレッドIDを切り出す pid_hex=`echo "${line}" | awk -F"," '{print $1}'` # スレッド名を切り出す thread_name=`echo "${line}" | awk -F"," '{print $2}'` # スレッドIDを10進数に変換 pid=`printf '%d\n' ${pid_hex}` # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する # 次のsmaps出力サンプルに★コメントの部分は取得詳細ロジックとなります。 # # 7ff751076000-7ff751079000 ---p 00000000 00:00 0 # Size: 12 kB ★説明: StackRedPages(4Kb) + StackYellowPages(8Kb) = 12Kb # Rss: 0 kB # Pss: 0 kB # Shared_Clean: 0 kB # Shared_Dirty: 0 kB # Private_Clean: 0 kB # Private_Dirty: 0 kB # Referenced: 0 kB # Anonymous: 0 kB # AnonHugePages: 0 kB # Swap: 0 kB # KernelPageSize: 4 kB # MMUPageSize: 4 kB # Locked: 0 kB # VmFlags: mr mw me ac ★説明:書き込み不可 # 7ff751079000-7ff751177000 rw-p 00000000 00:00 0 [stack:21275] # Size: 1016 kB ★説明: ここからNormal Stackページ # Rss: 108 kB # Pss: 108 kB # Shared_Clean: 0 kB # Shared_Dirty: 0 kB # Private_Clean: 0 kB # Private_Dirty: 108 kB # Referenced: 108 kB # Anonymous: 108 kB # AnonHugePages: 0 kB # Swap: 0 kB # KernelPageSize: 4 kB # MMUPageSize: 4 kB # Locked: 0 kB # VmFlags: rd wr mr mw me ac guard_page=`cat /proc/${PID}/smaps | grep -B16 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` stack_page=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` used_size=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Rss:" | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%15s\t%15s\t%14s\t%s\n" "${pid}" "${stack_size}" "${guard_page}" "${used_size}" "${thread_name}" done
3 テスト用プログラム
package test01; public class TestTask implements Runnable { @Override public void run() { add1(1); } private int add1(int a) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } return add2(a++); } private int add2(int a) { //add1 -> add2 --> add1 無限ループの状態で //スタックを食い潰す return add1(a--); } public static void main(String[] args) throws Exception { Thread t1 = new Thread(new TestTask()); Thread t2 = new Thread(new TestTask()); t1.start(); t2.start(); t1.join(); t2.join(); } }
4 監視してみる
5 出力形式
[ PID ] [StackSize(kB)] [GuardPage(kB)] [UsedSize(kB)] [Thread Name] 16559 1044 12 116 main 16560 1112 4 92 GC task thread#0 (ParallelGC) 16561 1028 4 8 GC task thread#1 (ParallelGC) 16562 1028 4 8 GC task thread#2 (ParallelGC) 16563 1028 4 8 GC task thread#3 (ParallelGC) 16564 12336 4 56 VM Thread 16565 1028 12 88 Reference Handler 16566 1028 12 88 Finalizer 16567 1028 12 92 Signal Dispatcher 16568 1028 12 20 C2 CompilerThread0 16569 1028 12 8 C2 CompilerThread1 16570 1028 12 12 C1 CompilerThread2 16571 1028 12 8 Service Thread 16572 1028 4 8 VM Periodic Task Thread 16573 1028 12 428 Thread-0 16583 1028 12 228 Thread-1 16613 1028 12 12 Attach Listener
項目名 | 説明 |
---|---|
PID | JavaスレッドのプロセスID |
StackSize | Javaスタック+Nativeスタックのサイズ (kB) |
GuardPage | スタック保護ページ (kB) |
UsedSize | 実際使った物理メモリサイズ (kB) |
Thread Name | スレッド名 |
バッチリ!便利な道具が1つ増えました。
6 2015/08/05 更新
前の実装は watch
コマンドでスクリプトを繰り返し実行させスタックサイズの監視をしてい
たのですが、繰り返しサイクルで毎回jstackでスレッドダンプを取得しているため若干JVMにオー
バヘッドがかかる。特にスレッドを多数起動されたアプリケーションサーバの場合性能へ影響
が無視できないので、少しスクリプトの実装シナリオを変えて改善してみました。
基本コンセプトは
jstack
を1回のみ実行する、その結果(スレッドダンプ)をtmpファイルで保持するps -H <PID>
でスレッドIDを取得して、1のスレッドダンプからスレッド名を引き- スレッドダンプから該当するものがない場合、スレッドダンプが古いと判断し再度
jstack
で新しいスレッドダンプを取る
#!/bin/sh ########################################################################### # jvm_stacksize.sh - take jvm stack size snapshot # # Authors: Akira Wakana <jalen.cn@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # Usage: # $jvm_stacksize.sh <JVM ProcessID> ########################################################################### print_stacksize() { rm -rf ${tmpdir}/stacksize.txt printf "[ PID ]\t[StackSize(kB)]\t[GuardPage(kB)]\t[UsedSize(kB)]\t[Thread Name]\n" ps h -L --format=lwp ${PID} | grep -v "${PID}" | while read pid do # スレッドID pid_hex=`printf '%#x\n' $pid` # スレッド名を切り出す threadinfo=`cat ${threadtdump} | fgrep " nid=${pid_hex} " | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/"` if [ "${threadinfo}" == "" ]; then jstack ${PID} > ${threadtdump} threadinfo=`cat ${threadtdump} | fgrep " nid=${pid_hex} " | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/"` fi thread_name=`echo "${threadinfo}" | awk -F"," '{print $2}'` # # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する guard_page=`cat /proc/${PID}/smaps | grep -B16 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` stack_page=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` used_size=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Rss:" | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%15s\t%15s\t%14s\t%s\n" "${pid}" "${stack_size}" "${guard_page}" "${used_size}" "${thread_name}" done > ${tmpdir}/stacksize.txt sort -r -k4,4 ${tmpdir}/stacksize.txt } finally_func() { RET=$? if [ -d ${tmpdir} ]; then rm -rf ${tmpdir} fi exit ${RET} } trap finally_func EXIT PID=$1 ps ${PID} | grep [j]ava > /dev/null || { echo "-----------------------------------------------------------------------" ps -ef | grep [j]ava echo "-----------------------------------------------------------------------" echo -n "please input the java process id: " read PID } export PID export tmpdir=`mktemp -d` export threadtdump=${tmpdir}/${PID}.tdump jstack ${PID} > ${threadtdump} export -f print_stacksize watch "bash -c print_stacksize" rm -rf ${tmpdir}
一般的なアプリでは実行中スレッドの数が頻繁に変わらないので、これで満足しています。;-)