~どうやって情報をとってるの?~
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
jdb
jhat
jinfo
jmap
jps
jcontrol
jsadebugd
jstack
jstat
jstatd
Serviceability Technologies
http://openjdk.java.net/groups/serviceability/
ツールと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>0
 AttachListenerに与える命令
 ヌル文字(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>
:
ソケットバッファ
溢れに注意!
JVMにインパクトを与えずにディープな情報を取得!
jvmstat Performance Counters
 通称”PerfCounter”
 HotSpotの統計情報を集めたファイル
 /tmp/hsperfdata_<ユーザー名>/<PID>
 各種データは、このファイルにマップされた
仮想メモリ空間に直接保存
 GC回数/時間
 ライブスレッド数
 etc…
 各種ツールはこのファイルを直接参照して
JVMの動作状況を収集する
具体的に見てみる
 jcmdのPerfCounter.print
$ jcmd 2065 PerfCounter.print
2065:
java.ci.totalTime=0
java.cls.loadedClasses=379
java.cls.sharedLoadedClasses=0
java.cls.sharedUnloadedClasses=0
java.cls.unloadedClasses=0
:
sun.rt.safepointSyncTime=22
sun.rt.safepointTime=80
sun.rt.safepoints=1
sun.rt.threadInterruptSignaled=0
sun.rt.vmInitDoneTime=1365171171705
sun.threads.vmOperationTime=29
sun.urlClassLoader.readClassBytesTime=165372
sun.zip.zipFile.openTime=1010449
sun.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 defined
 6938627が入ったことで、各種ツールが動かなく
なってしまう人が続出
○ 実は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_options
option 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 1806
Threads MonitorContention STW ThreadA ThreadB ThreadC
7 21 0.117 7 6 5
インターバル設定しても
変化がみられません
8011934 : sun.misc.PerfCounter calls
Perf.createLong with incorrect parameters
ネイティブレイヤから直接情報を引っこ抜く最強ツール!
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 8675
Attaching to process ID 8675, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 14.0-b16
Deadlock 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.UnmappedAddressExce
ption when it should not
○ 全JDKに影響
○ 2012/01にパッチを出したものの、なかなか入れても
らえない…
$ /usr/local/jdk1.6.0/bin/jstack /usr/local/jdk1.6.0/bin/java core.1249
Attaching 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.patch
patching file agent/src/os/linux/ps_core.c
patch unexpectedly ends in middle of line
Hunk #2 succeeded at 711 with fuzz 1 (offset -1 lines).
3.ビルド
 最近のOpenJDKはconfigure→make
 依存関係は適宜解決してください…
$ chmod a+x configure
$ ./configure MILESTONE=TypeS
Running 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.1249
Attaching 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-b105
Deadlock 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 PRODUCT
class 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で動作
 jstack
 jmap
結論
 実行にSTWを伴う
 JVMの状態によっては処理が高コストになり
うる
上記以外であればLowでOK
…と、勝手に解釈
2.改造
 CodeCacheの全走査になるので、とりあえず
Mediumに設定
#ifndef PRODUCT
class 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 PRODUCT
void 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/jcmd
31376 org.apache.catalina.startup.Bootstrap start
31390 sun.tools.jcmd.JCmd
$ ./build/linux-x86_64-normal-server-fastdebug/jdk/bin/jcmd 31376 CodeCache.print
31376:
java.util.zip.InflaterInputStream.ensureOpen()V alive
java.lang.Integer.rotateLeft(II)I alive
java.util.zip.ZStreamRef.address()J alive
java.lang.String.charAt(I)C alive
java.lang.String.indexOf(II)I alive
sun.misc.Hashing.murmur3_32(I[CII)I alive
java.lang.Object.<init>()V alive
sun.nio.cs.UTF_8$Encoder.encode([CII[B)I alive
java.lang.String.equals(Ljava/lang/Object;)Z alive
java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V alive
java.lang.String.lastIndexOf(II)I alive
:
何か、新しいツールはないだろうか?
JVMからOSまで低オーバーヘッドで情報収集!
HeapStats
 障害解析に必要な情報を根こそぎ収集!
 GC動作状況、直後のクラスヒストグラム
 OSリソース(CPU/メモリ)
 SPECjvm2008でのオーバーヘッドは5%以下
 OOMEやデッドロックをリアルタイム検知
 SNMPトラップも送出します
 解析はGUIツールでラクチン!
 OS・Javaの情報を横並びで比較可能
Serviceability Toolsのエッセンスも実装!
こんなにいっぱい見れま
す!
HeapStats Community
 HeapStats @ IcedTea
 http://icedtea.classpath.org/wiki/HeapStats
 ML
 heapstats@icedtea.classpath.org
 http://icedtea.classpath.org/mailman/listinfo/heapstats
Serviceability Toolsの裏側

Serviceability Toolsの裏側