serviceability toolsの裏側
TRANSCRIPT
Serviceability Toolsの裏側~どうやって情報をとってるの?~
2013/05/11末永 恭正 @YaSuenag #ccc_r54
自己紹介 末永 恭正 @YaSuenag
某通信キャリアで Java の障害解析してますしたhs_error ログやコアの解析 などなど
ネイティブ寄りな Java プログラマ 基本サンデープログラマー
Pascal とか C とか Java とか…
すえなが やすまさ
Serviceability Tools ?
Serviceability in the J2SE Repository http://openjdk.java.net/groups/serviceability/svcjdk.html
jconsole
jdbjhat
jinfo
jmap
jpsjcontrol
jsadebugd
jstack
jstat
jstatd
Serviceability Technologies
http://openjdk.java.net/groups/serviceability/
Dynamic Attachツールと JVM がカンタンに連携
Dynamic Attach
ツールからの要求に応じて HotSpot が情報収集 各ツールと HotSpot の連係プレー
ツール側○ sun.tools.attach.HotSpotVirtualMachine の
提供するメソッドを実行○ 専用のコマンドと引数をターゲット VM へ送信
HotSpot 側○ Attach Listener が指定された処理を実行し、結果を
ツールへ返却"Attach Listener" daemon prio=10 tid=0x00007f92dc001000 nid=0x261e runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
各ツールと HotSpot の連携
Java プロセス( HotSpot VM )
各種ツール
①SIGQUIT 送信
※Linux の場合
②AttachListener起動
③ コマンド送信
④ 処理実行
④´ 処理結果
Unix ドメインソケット
AttachListener のコマンド フォーマット
<ver>\0<cmd>\0<arg>\0<arg>\0<arg>\0AttachListener に与える命令ヌル文字( \0 )がデリミタ
項目ver
○ プロトコルバージョン(’ 1’ 固定)cmd
○ コマンドarg
○ コマンド引数(最大 3 つまで)
AttachListener のコマンドコマンド 機能 利用ツールagentProperties エージェント用システムプロパ
ティの取得• jdb• jconsole• JMX エージェント
datadump SIGQUIT 送出(と等価) なしdumpheap ヒープダンプ生成 jmap
load JVMTI エージェントのロード jconsole
properties システムプロパティの取得 • jconsole• jinfo
threaddump スレッドダンプの取得 jstack
inspectheap クラスヒストグラムの取得 jmap
setflag 非推奨オプション (-XX) の設定 jinfo
printflag 非推奨オプション (-XX) の取得 jinfo
jcmd jcmd の実行 jcmd
enabledprobes DTrace プローブを有効にする※Solaris のみ
なし
超簡単! AttachListener 利用 Java プログラム中の特定のポイント通過時の
クラスヒストグラムを取ってみるJava プログラムから、自分自身の VM にアタッチHotSpotVirtualMachine#heapHisto() を呼
び出す○ AttachListener 的には inspectheap コマンドを実
行するLinux の Java7 以降で動くものを作ります
Java プログラム ※抜粋import java.io.*;import java.nio.file.*;import com.sun.tools.attach.*;import sun.tools.attach.*;
: Path selfProc = FileSystems.getDefault().getPath("/proc/self"); String pidString = Files.readSymbolicLink(selfProc).toString();
HotSpotVirtualMachine selfVM = HotSpotVirtualMachine)VirtualMachine.attach(pidString);
try(InputStream in = selfVM.heapHisto("-all")){ byte[] buf = new byte[1024]; int n;
while((n = in.read(buf)) > 0){ System.out.print(new String(buf, 0, n, "UTF-8")); }
} finally{ selfVM.detach(); }
:
PID 取得
アタッチ
AttachListener へコマンド送信
結果読み込み&
出力デタッチ
ビルド&実行$ javac -cp $JAVA_HOME/lib/tools.jar AttachListenerTest.java$ java -cp $JAVA_HOME/lib/tools.jar:. AttachListenerTest
num #instances #bytes class name---------------------------------------------- 1: 13228 1462888 [C 2: 7477 1136592 <constMethodKlass> 3: 7477 1022984 <methodKlass> 4: 562 701568 <constantPoolKlass> 5: 2093 536728 [B 6: 562 396376 <instanceKlassKlass> 7: 502 395520 <constantPoolCacheKlass> 8: 579 265768 [I 9: 6120 146880 java.lang.String 10: 631 76696 java.lang.Class 11: 1003 65664 [S 12: 882 55808 [Ljava.lang.Object; 13: 2278 54672 java.lang.StringBuilder 14: 900 52952 [[I 15: 52 29536 <objArrayKlassKlass>
:
ソケットバッファ溢れに注意!
jvmstat Performance Counters
JVM にインパクトを与えずにディープな情報を取得!
jvmstat Performance Counters
通称” PerfCounter” HotSpot の統計情報を集めたファイル
/tmp/hsperfdata_< ユーザー名 >/<PID> 各種データは、このファイルにマップされた
仮想メモリ空間に直接保存GC 回数/時間ライブスレッド数etc…
各種ツールはこのファイルを直接参照してJVM の動作状況を収集する
具体的に見てみる jcmd の PerfCounter.print
$ jcmd 2065 PerfCounter.print2065:java.ci.totalTime=0java.cls.loadedClasses=379java.cls.sharedLoadedClasses=0java.cls.sharedUnloadedClasses=0java.cls.unloadedClasses=0
:sun.rt.safepointSyncTime=22sun.rt.safepointTime=80sun.rt.safepoints=1sun.rt.threadInterruptSignaled=0sun.rt.vmInitDoneTime=1365171171705sun.threads.vmOperationTime=29sun.urlClassLoader.readClassBytesTime=165372sun.zip.zipFile.openTime=1010449sun.zip.zipFiles=2
Java レイヤから出してるPerfCounter
6878481: Add performance counters in the JDK
トラップ!その1 tmpwatch
/tmp や /var/tmp を定期的にお掃除するツール○ Red Hat 系ディストロではおなじみ
/tmp/hsperfdata_< ユーザー名 > も消しやがる!○ ps ではプロセスが見えるのに JDK 付属ツールでア
タッチできない場合に疑うべきポイント○ RHBZ#527425 - /tmp/hsperfdata_${user}/${PID} is
deleted by tmpwatchやっぱり同じことで困ってる人がいましたFedora なら tmpwatch-2.9.16-1.fc13 から OK だそうです
トラップ!その2 java.io.tmpdir
6u23 、 6u24 のみ該当○ 6938627: Make temporary directory use property
java.io.tmpdir when specified○ 7009828: Fix for 6938627 breaks visualvm
monitoring when -Djava.io.tmpdir is defined6938627 が入ったことで、各種ツールが動かな
くなってしまう人が続出○ 実は Dynamic Attach も影響を受けます
HotSpot混迷の時代
超簡単!カスタム情報収集ツール PerfCounter から自分が欲しい情報を好きなフォーマットで出力します
出力する情報ライブスレッド数モニタ競合回数VM 総停止時間アプリケーション固有の統計情報
○ プログラムから PerfCounter にエントリを作ります
Java プログラム ※抜粋
PerfCounter 生成メソッドsun.misc.PerfCounter#newPerfCounter を
呼び出すPackage private なのでリフレクションで呼び出
しprivate PerfCounter newPerfCounter(String name) throws Exception{ Method method = PerfCounter.class.getDeclaredMethod( "newPerfCounter", String.class); method.setAccessible(true); PerfCounter result = (PerfCounter)method.invoke(null, name); return result;}
Java プログラム ※抜粋
呼び出しエントリ名はとりあえず以下のようにしてみる
○ perfCounter.test.< スレッド名>.invocations
String entryName = “perfCounter.test.” + Thread.currentThread().getName() + ".invocations";
try{ this.perfCounter = this.newPerfCounter(entryName);
jstat_options
jstat が使用する定義ファイル PerfCounter から収集する項目と出力方法を
定義したもの割合みたいな単純計算まで定義できます
$JAVA_HOME/lib/tools.jar に含まれてます$HOME/.jvmstat/jstat_options でオーバー
ライド可能
カスタム版 jstat_optionsoption custom{ column { header "^Threads^" data java.threads.live scale raw align right width 4 format "0" } column { header "^MonitorContention^" data sun.rt._sync_Parks scale raw align right width 4 format "0" } column { header "^STW^" data (sun.rt.safepointTime/sun.os.hrt.frequency) scale raw align right width 6 format "0.000" }
column { header "^ThreadA^" data perfCounter.test.ThreadA.invocations scale raw align right width 3 format "0" } column { header "^ThreadB^" data perfCounter.test.ThreadB.invocations scale raw align right width 3 format "0" } column { header "^ThreadC^" data perfCounter.test.ThreadC.invocations scale raw align right width 3 format "0" }}
実行結果$ jstat -custom 1806Threads MonitorContention STW ThreadA ThreadB ThreadC 7 21 0.117 7 6 5
インターバル設定しても変化がみられません
8011934 : sun.misc.PerfCounter calls Perf.createLong with incorrect parameters
Serviceability Agentネイティブレイヤから直接情報を引っこ抜く最強ツール!
Serviceability Agent
通称” SA” 機能
強制的なプロセスアタッチ○ Linux では ptrace(2) を使用○ jinfo や jmap 等の” -F” オプションで発動○ jstack では” -m” オプションでも発動
コア解析○ Linux ではコアイメージから Java レイヤの情報
(スレッドダンプやヒープダンプ)などを取得可能
○ ELF バイナリを debuginfo まで含め、自力でパースしてしまう!
SA の情報ソース① シンボルテーブル
各バイナリを解析して、仮想メモリ空間のどこに、何が入っているのかを特定
② VMStructs JVM 中で使用する情報を保持する仮想メモリア
ドレスや各種オフセット位置をまとめた構造体配列
Java オブジェクトの C++表現( oop )のパースなど、様々な用途で使用
VMStructs の中身 マクロで定義されてます
hotspot/src/share/vm/runtime/vmStructs.cpp
:nonstatic_field(CollectedHeap, _reserved, MemRegion) \nonstatic_field(SharedHeap, _perm_gen, PermGen*) \nonstatic_field(CollectedHeap, _barrier_set, BarrierSet*) \nonstatic_field(CollectedHeap, _defer_initial_card_mark, bool) \nonstatic_field(CollectedHeap, _is_gc_active, bool) \nonstatic_field(CompactibleSpace, _compaction_top, HeapWord*) \nonstatic_field(CompactibleSpace, _first_dead, HeapWord*) \nonstatic_field(CompactibleSpace, _end_of_live, HeapWord*) \
:
トラップ!その1 Linux x86_64環境で動かないことがあります
7003789: PTRACE_GETREGS problems with SA on Linux.○ JDK6u25 、 JDK7 以降なら Fix済み
$ /usr/local/jdk1.6.0_14/bin/jstack -F 8675Attaching to process ID 8675, please wait...Debugger attached successfully.Server compiler detected.JVM version is 14.0-b16Deadlock Detection:
No deadlocks found.
Thread 8693: (state = BLOCKED)Error occurred during stack walking:sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: get_thread_regs failed for a lwp
トラップ!その2 Linux環境でコアイメージを読み込めないこ
とがあります7133122: SA throws
sun.jvm.hotspot.debugger.UnmappedAddressException when it should not○ 全 JDK に影響○ 2012/01 にパッチを出したものの、なかなか入れても
らえない…
$ /usr/local/jdk1.6.0/bin/jstack /usr/local/jdk1.6.0/bin/java core.1249Attaching to core core.1249 from executable /usr/local/jdk1.6.0/bin/java, please wait...Error attaching to core file: Can't attach to the core file
じゃあ、直してみる OpenJDK パッチ当て 3 分クッキング
1. OpenJDK のソースを落としてきます2. パッチを当てます3. ビルドします4. libsaproc.so を、使いたい JDK にコピーし
ます
これだけ!
1. OpenJDK のソース入手 mercurial (hg) を使います
$ hg clone http://hg.openjdk.java.net/jdk8/jdk8複製先ディレクトリ : jdk8全リビジョンを取得中リビジョンを追加中マニフェストを追加中ファイルの変更を追加中666 個のリビジョン (871 の変更を 114 ファイルに適用 ) を追加ブランチ default へ更新中ファイルの更新数 97 、 マージ数 0 、 削除数 0 、 衝突未解消数 0$ cd jdk8/$ sh get_source.sh# Repositories: corba jaxp jaxws langtools jdk hotspot nashorn
:
リポジトリの clone
forest構成リポジトリの取得
2.パッチ当て パッチを入手します
CR#7133122 の Comments からコピペserviceability-dev の投稿メールから取得
○ http://mail.openjdk.java.net/pipermail/serviceability-dev/2012-January/005174.html
JDK8 の hotspot で patch します$ patch -p1 < address_mapping.patchpatching file agent/src/os/linux/ps_core.cpatch unexpectedly ends in middle of lineHunk #2 succeeded at 711 with fuzz 1 (offset -1 lines).
3.ビルド 最近の OpenJDK は configure→make
依存関係は適宜解決してください…
$ chmod a+x configure$ ./configure MILESTONE=TypeSRunning generated-configure.sh
:* Memory limit: 2000 MB* ccache status: installed and in use
$ make:
configure に実行権限が
ついていません
MILESTONE で好きな文字列をバージョンに入れられます
4. libsaproc の移植 SA のキモです 関数インターフェースがそんなに変わらない
(と思う)ので JDK6/7 へ持って行っても大丈夫(なはず)
libsaproc.so をコンパイルしたものに入れ替え$JAVA_HOME/jre/lib/<CPU> にあります<CPU> には” amd64” や” i386” などが入ります
コアを食べさせてみる JDK6 GA で試してみました
$ /usr/local/jdk1.6.0/bin/jstack /usr/local/jdk1.6.0/bin/java core.1249Attaching to core core.1249 from executable /usr/local/jdk1.6.0/bin/java, please wait...Debugger attached successfully.Server compiler detected.JVM version is 1.6.0-b105Deadlock Detection:
No deadlocks found.
Thread 1254: (state = BLOCKED)
Thread 1253: (state = BLOCKED) - java.lang.Object.wait(long) @bci=0 (Interpreted frame) - java.lang.ref.ReferenceQueue.remove(long) @bci=44, line=116 (Interpreted frame) - java.lang.ref.ReferenceQueue.remove() @bci=2, line=132 (Interpreted frame) - java.lang.ref.Finalizer$FinalizerThread.run() @bci=3, line=159 (Interpreted frame)
:
動いた!
なにか忘れてない?http://openjdk.java.net/groups/serviceability/
jcmd
jcmd
Dynamic Attach と PerfCounter の両面を併せ持つツール
コマンド実装は JVM 側だけで OK !機能追加/改造がラクチン!
じゃあ、改造してみる OpenJDK jcmd改造 3 分クッキング
とりあえず、コンパイル済みコードの一覧を出してみます○ CodeCache の内容をダンプしてみます○ CodeCache.print というコマンドにしてみます
1. OpenJDK のソースを落としてきます2. jcmd の実装部を追加します3. ビルドします
これだけ!
1. OpenJDK のソース入手 省略
2.改造 下ごしらえ
CodeCache::print_internal が stdout 以外のストリームに出力できるようにします○ hotspot/src/share/vm/code/codeCache.cpp
○ hotspot/src/share/vm/code/codeCache.hpp
static void print_internals(outputStream *tty = tty);
出力先ストリーム追加
void CodeCache::print_internals(outputStream *tty) {
2.改造 DiagnosticCommand 用クラスの定義
hotspot/src/share/vm/services/diagnosticCommand.hpp
#ifndef PRODUCTclass PrintCodeCacheDCmd : public DCmd {public: PrintCodeCacheDCmd(outputStream* output, bool heap) : DCmd(output,heap) { } static const char* name() { return "CodeCache.print"; } static const char* description() { return "Print compiled methods in CodeCache."; }
static const char* impact() { return “???"; } static int num_arguments() { return 0; } virtual void execute(TRAPS);};#endif
どうしよ…?
“Impact” とは? 各コマンドの help で表示される部分
JEP 137: Diagnostic-Command Framework定義がものすごく曖昧
○ JVM への影響度合いを Low / Medium / High で分類○ 膨大なスレッドダンプやヒープダンプは深刻、バー
ジョン番号等の取得は大した影響なしと定義http://openjdk.java.net/jeps/137
$ jcmd 1563 help VM.flags:
Impact: Low::
各コマンドとインパクトコマンド 内容 インパクトhelp コマンドのヘルプ表示 Low
VM.version JVM バージョンの表示 Low
VM.command_line コマンドライン引数の表示 Low
VM.system_properties システムプロパティの表示 Low
VM.flags 非推奨( -XX )オプションの表示 Low
VM.uptime JVM 起動時間の表示 Low
GC.run_finalization ファイナライザの実行 Medium
GC.run GC の実行 Medium
GC.heap_dump ヒープダンプの生成 High
GC.class_histogram クラスヒストグラムの表示 High
GC.class_stats クラス統計情報の表示 High
Thread.print スレッドダンプの表示 Medium
ManagementAgent.start JMX エージェントの起動 No impact
ManagementAgent.start_local JMX エージェントの起動(ローカル限定)
No impact
ManagementAgent.stop JMX エージェントの停止 No impact
safepoint “Depends on Java content.”
safepoint
いわゆる STWアプリケーションスレッド全停止正式な定義はコチラ
○ http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html#safepoint
jcmd では、 STW を伴うコマンドは最低でもMedium 以上に定義されている模様
Dynamic Attach では以下が safepoint で動作jstackjmap
結論 実行に STW を伴う JVM の状態によっては処理が高コストになり
うる
上記以外であれば Lowで OK… と、勝手に解釈
2.改造 CodeCache の全走査になるので、とりあえず
Medium に設定#ifndef PRODUCTclass PrintCodeCacheDCmd : public DCmd {public: PrintCodeCacheDCmd(outputStream* output, bool heap) : DCmd(output,heap) { } static const char* name() { return "CodeCache.print"; } static const char* description() { return "Print compiled methods in CodeCache."; } static const char* impact() { return “Medium"; } static int num_arguments() { return 0; } virtual void execute(TRAPS);};#endif
2.改造 PrintCodeCacheDCmd の登録
void DCmdRegistrant::register_dcmds()に初期化コードを追加
hotspot/src/share/vm/services/diagnosticCommand.cpp
#ifndef PRODUCT DCmdFactory::register_DCmdFactory( new DCmdFactoryImpl<PrintCodeCacheDCmd>(true, false));#endif
2.改造 コマンド実行関数の実装
hotspot/src/share/vm/services/diagnosticCommand.cpp
#ifndef PRODUCTvoid PrintCodeCacheDCmd::execute(TRAPS) { MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); CodeCache::print_internals(output());}#endif
ロック忘れずに!
3.ビルド 省略ですが…
debug モードでビルド!./configure --with-debug-level=fastdebug
実行結果 Tomcat7 に jcmd
CATALINA_OPTS に -XX:+Verbose を付加$ ./build/linux-x86_64-normal-server-fastdebug/jdk/bin/jcmd31376 org.apache.catalina.startup.Bootstrap start31390 sun.tools.jcmd.JCmd$ ./build/linux-x86_64-normal-server-fastdebug/jdk/bin/jcmd 31376 CodeCache.print31376:java.util.zip.InflaterInputStream.ensureOpen()V alivejava.lang.Integer.rotateLeft(II)I alivejava.util.zip.ZStreamRef.address()J alivejava.lang.String.charAt(I)C alivejava.lang.String.indexOf(II)I alivesun.misc.Hashing.murmur3_32(I[CII)I alivejava.lang.Object.<init>()V alivesun.nio.cs.UTF_8$Encoder.encode([CII[B)I alivejava.lang.String.equals(Ljava/lang/Object;)Z alivejava.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V alivejava.lang.String.lastIndexOf(II)I alive
:
何か、新しいツールはないだろうか?
HeapStatsJVM から OS まで低オーバーヘッドで情報収集!
HeapStats
障害解析に必要な情報を根こそぎ収集!GC 動作状況、直後のクラスヒストグラムOS リソース( CPU/メモリ)SPECjvm2008 でのオーバーヘッドは 5% 以下
OOME やデッドロックをリアルタイム検知SNMP トラップも送出します
解析は GUI ツールでラクチン!OS ・ Java の情報を横並びで比較可能
Serviceability Toolsのエッセンスも実装!
こんなにいっぱい見れます!
HeapStats Community
HeapStats @ IcedTeahttp://icedtea.classpath.org/wiki/HeapStats
[email protected]://icedtea.classpath.org/mailman/listinfo/heapstats
?