Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
© 2020 NTT DATA Corporation
JVMに裏から手を出す!
JVMTIに触れてみよう
2020年9月19日
株式会社NTTデータ 技術開発本部
末永 恭正
オープンソースカンファレンス2020 Online/Hiroshi...
© 2020 NTT DATA Corporation 2
自己紹介
• 末永 恭正
• NTTデータ 技術革新統括本部
• OpenJDK Reviewer
YaSuenag
© 2020 NTT DATA Corporation 3
本日のテーマ
Java
Virtual
Machine
Tool
Interface
Java仮想マシンが備える
Javaのデバッグや監視に便利な
インターフェース(API)
© 2020 NTT DATA Corporation 4
https://docs.oracle.com/javase/jp/14/
https://docs.oracle.com/javase/jp/14/docs/specs/jvmti....
© 2020 NTT DATA Corporation 5
JVMTI
• APIインターフェース:C/C++
• JNI+αなイメージ
• メソッド呼び出しなど、JNIもよく使います
• 共有ライブラリ形式のイベントドリブンなエージェントプロ...
© 2020 NTT DATA Corporation 6
イベントドリブン
エージェント
ロード
JVM開始 JVM終了 エージェント
アンロード
スレッド開始
クラスロード
GC
例外発生 メソッド実行
エージェント
アタッチ
…
• JV...
© 2020 NTT DATA Corporation 7
JVMTIエージェントを作る、ということ
+ JVM実装固有
イベント
JVMTI定義イベント
やりたいことのトリガとなるイベントを見つけて
できるだけ軽い動作をさせる!
© 2020 NTT DATA Corporation 8
実は身近!?JVMTIの利用例
• IDE、エディタのデバッガの足廻り
• Java Debugger Interface(JDI)
• EclipseやVS Codeなどで使われる、...
© 2020 NTT DATA Corporation
サンプルで理解する
JVMTI
© 2020 NTT DATA Corporation 10
3つのサンプル
1. Hello World
• JVMTIエージェントの開始/終了を体験する
• 2つの開始タイミングを体験する
2. はじめてのイベントフック
• JVM内部イベ...
© 2020 NTT DATA Corporation 11
JVMTIエージェントを作る際のポイント
1. CとC++で書き方が違う
2. 1エージェント 1 JVMTI環境
3. 段階(フェーズ)を意識する
© 2020 NTT DATA Corporation 12
ポイント1:CとC++で書き方が違う
(*jvmti)->GetTime(jvmti, &value); jvmti->GetTime(&value);
C言語 C++
• JVMT...
© 2020 NTT DATA Corporation 13
ポイント2:1エージェント 1 JVMTI環境
• JVMTIの機能へはJVMTI環境経由でアクセスする
• jvmtiEnv型
• すべてのJVMTI機能はJVMTI環境単位で管理...
© 2020 NTT DATA Corporation 14
ポイント2:段階(フェーズ)を意識する
• JVMTIのイベントや関数では、それが使える「段階」が存在する
• 各段階に応じて使える機能が違うので注意が必要
OnLoad段階 JVM...
© 2020 NTT DATA Corporation
JVMTIで
Hello World
© 2020 NTT DATA Corporation 16
概要
• エージェントの開始/終了時にコンソールにメッセージを出す
• 動的アタッチにも対応する
• オプション文字列に”error”が与えられたとき、エラーにする
• サンプルソー...
© 2020 NTT DATA Corporation 17
エージェント開始時 ~プロセス起動時~
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options,
void *re...
© 2020 NTT DATA Corporation 18
エージェント開始時 ~動的アタッチ~
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options,
void *r...
© 2020 NTT DATA Corporation 19
エージェント終了時
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm){
printf("Goodbye World from Age...
© 2020 NTT DATA Corporation 20
ビルド
cmake_minimum_required(VERSION 3.0)
project(HelloWorld C)
find_package(JNI REQUIRED)
fi...
© 2020 NTT DATA Corporation 21
実行
$ java -agentpath:/path/to/libhelloworld.so --version
Hello World from Agent_OnLoad()
op...
© 2020 NTT DATA Corporation 22
動的アタッチ
$ jcmd 2875 JVMTI.agent_load /path/to/libhelloworld.so
2875:
return code: 0
• JVMTI....
© 2020 NTT DATA Corporation 23
エラーの場合
$ java -agentpath:/path/to/libhelloworld.so=error --version
Hello World from Agent_O...
© 2020 NTT DATA Corporation
はじめての
イベントフック
© 2020 NTT DATA Corporation 25
概要
• OutOfMemoryErrorをフックし、コンソールに出力します
• ResourceExhaustedイベントを使います
• https://docs.oracle.c...
© 2020 NTT DATA Corporation 26
コードの流れ
① JVMTI環境を得る
② 権限を設定する
③ イベントコールバックを設定する
④ イベントを有効化する
© 2020 NTT DATA Corporation 27
①JVMTI環境を得る
• JavaVM::GetEnv()の仕様にJVMTIバージョンを与えて
jvmtiEnv型のポインタを取得する
• バージョン番号は使用する機能の導入された...
© 2020 NTT DATA Corporation 28
②権限を設定する
• 使用する関数やイベントで必要な権限を追加する
• 設定可能な権限はJVM実装や実行段階によって異なる
• 現在の権限の取得はGetCapabilities()
...
© 2020 NTT DATA Corporation 29
③イベントコールバックを設定する
• コールバック関数を追加する
• 1つのJVMTI環境につきコールバックは1つしか設定できない
• 例:ResourceExhaustedは1環境...
© 2020 NTT DATA Corporation 30
④イベントを有効化する
• すべてのイベントはデフォルトで無効になっている
• JVMTIドキュメントの「有効化」(Enabling)を参照
jvmti->SetEventNotif...
© 2020 NTT DATA Corporation 31
実行例
$ ./run.sh /path/to/liboomehook.so
from JVMTI: OutOfMemoryError occurred: Java heap spa...
© 2020 NTT DATA Corporation
応用編:
自家製GCログ
© 2020 NTT DATA Corporation 33
概要
• アプリケーション停止を伴うGCをフックし、コンソールに出力します
• GarbageCollection{Start,Finish}イベントを使います
• https://...
© 2020 NTT DATA Corporation 34
GCイベントのワナ
GarbageCollectionFinishGarbageCollectionStart
アプリケーションスレッド停止区間
アプリケーション停止中にイベント発生!
© 2020 NTT DATA Corporation 35
実験:GC開始イベントで10秒スリープさせる
void JNICALL OnGarbageCollectionStart(jvmtiEnv *jvmti){
std::cout <<...
© 2020 NTT DATA Corporation 36
実行例
[0.026s][info][safepoint] Entering safepoint region: G1CollectFull
from JVMTI: GC start...
© 2020 NTT DATA Corporation 37
見る場所によって異なる”停止時間”
停止要求
アプリケーションスレッド停止
GarbageCollectionStart
GC
GarbageCollectionFinish
アプリ...
© 2020 NTT DATA Corporation 38
では、どうすればいいか?
別スレッドで動かす!
© 2020 NTT DATA Corporation 39
GCイベントとマルチスレッド
GCワーカー JVMTIのスレッド
停止要求
アプリケーションスレッド停止
GarbageCollectionStart
GC
GarbageColle...
© 2020 NTT DATA Corporation 40
注意
• GCワーカーと並行して動く
• アプリケーション完全停止中でも動き回れる
• Javaヒープを触った瞬間にGCによる完全停止に巻き込まれる!
jvmti->RawMonit...
© 2020 NTT DATA Corporation
まとめ
© 2020 NTT DATA Corporation 42
まとめ
• JVMTI:デバッグや監視に使えるイベントドリブンな共有ライブラリ
• イベントの種類やイベントハンドラの書き方によっては
アプリケーション性能へ甚大な影響を与えるため注...
© 2020 NTT DATA Corporation本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です
Nächste SlideShare
Wird geladen in …5
×

JVMに裏から手を出す!JVMTIに触れてみよう(オープンソースカンファレンス2020 Online/Hiroshima 講演資料)

JVMに裏から手を出す! JVMTIに触れてみよう
(オープンソースカンファレンス2020 Online/Hiroshima 講演資料)

2020年9月19日
株式会社NTTデータ 技術開発本部
末永 恭正

  • Als Erste(r) kommentieren

JVMに裏から手を出す!JVMTIに触れてみよう(オープンソースカンファレンス2020 Online/Hiroshima 講演資料)

  1. 1. © 2020 NTT DATA Corporation JVMに裏から手を出す! JVMTIに触れてみよう 2020年9月19日 株式会社NTTデータ 技術開発本部 末永 恭正 オープンソースカンファレンス2020 Online/Hiroshima
  2. 2. © 2020 NTT DATA Corporation 2 自己紹介 • 末永 恭正 • NTTデータ 技術革新統括本部 • OpenJDK Reviewer YaSuenag
  3. 3. © 2020 NTT DATA Corporation 3 本日のテーマ Java Virtual Machine Tool Interface Java仮想マシンが備える Javaのデバッグや監視に便利な インターフェース(API)
  4. 4. © 2020 NTT DATA Corporation 4 https://docs.oracle.com/javase/jp/14/ https://docs.oracle.com/javase/jp/14/docs/specs/jvmti.html
  5. 5. © 2020 NTT DATA Corporation 5 JVMTI • APIインターフェース:C/C++ • JNI+αなイメージ • メソッド呼び出しなど、JNIもよく使います • 共有ライブラリ形式のイベントドリブンなエージェントプログラム • 2つの開始タイミング(プロセス同期、動的アタッチ) • 機能 • JVM内部イベントのフック • クラス書き換え • オブジェクト参照関係の追跡 など
  6. 6. © 2020 NTT DATA Corporation 6 イベントドリブン エージェント ロード JVM開始 JVM終了 エージェント アンロード スレッド開始 クラスロード GC 例外発生 メソッド実行 エージェント アタッチ … • JVMの内部では様々なイベントが飛び交っている! • JVMのライフサイクルの中で様々なイベントが発生 • イベントをどう捌くかがJVMTIエージェントのキモ! • イベントを拾いすぎたり、ハンドラが重いとアプリケーションへ悪影響
  7. 7. © 2020 NTT DATA Corporation 7 JVMTIエージェントを作る、ということ + JVM実装固有 イベント JVMTI定義イベント やりたいことのトリガとなるイベントを見つけて できるだけ軽い動作をさせる!
  8. 8. © 2020 NTT DATA Corporation 8 実は身近!?JVMTIの利用例 • IDE、エディタのデバッガの足廻り • Java Debugger Interface(JDI) • EclipseやVS Codeなどで使われる、ポピュラーなもの • Java Flight Recorder • Java 11からOSS化された軽量なプロファイラ • クラス情報を差し替えるためJVMが内部的に利用 • HeapStats • メモリトラブル早期解決を助けるOSSの監視ツール • GC動作状況やメモリ不足発生をリアルタイムに把握
  9. 9. © 2020 NTT DATA Corporation サンプルで理解する JVMTI
  10. 10. © 2020 NTT DATA Corporation 10 3つのサンプル 1. Hello World • JVMTIエージェントの開始/終了を体験する • 2つの開始タイミングを体験する 2. はじめてのイベントフック • JVM内部イベントのフックを体験する • JVMの実行フェーズ、JVMTI機能の実行に必要な権限を 理解する 3. 自家製GCログ • JVMTIを活用した独自のロギング機能を作成する • JVMTIエージェントがJVMの挙動に与える影響を体験する https://github.com/YaSuenag/jvmti-examples
  11. 11. © 2020 NTT DATA Corporation 11 JVMTIエージェントを作る際のポイント 1. CとC++で書き方が違う 2. 1エージェント 1 JVMTI環境 3. 段階(フェーズ)を意識する
  12. 12. © 2020 NTT DATA Corporation 12 ポイント1:CとC++で書き方が違う (*jvmti)->GetTime(jvmti, &value); jvmti->GetTime(&value); C言語 C++ • JVMTI環境を逆参照する • 第1引数にJVMTI環境を渡す • 通常のメンバ関数呼び出し JNIと同じ!
  13. 13. © 2020 NTT DATA Corporation 13 ポイント2:1エージェント 1 JVMTI環境 • JVMTIの機能へはJVMTI環境経由でアクセスする • jvmtiEnv型 • すべてのJVMTI機能はJVMTI環境単位で管理される • イベント、動的メモリ、権限… • 基本的に1つのエージェントでは1つのJVMTI環境を利用する • コールバックの二重フックやメモリリークなどの問題を避けるため (*jvmti)->GetTime(jvmti, &value); jvmti->GetTime(&value);
  14. 14. © 2020 NTT DATA Corporation 14 ポイント2:段階(フェーズ)を意識する • JVMTIのイベントや関数では、それが使える「段階」が存在する • 各段階に応じて使える機能が違うので注意が必要 OnLoad段階 JVMが開始された直後の、JVMTIエージェントがロードされた段階 初期段階 JVMTIエージェントの初期化が完了し、JVMの初期化処理が始まる段階 開始段階 JVMの初期化中の段階 ライブ段階 動作中(アプリケーションが制限なく動作できる)段階 デッド段階 JVM終了の段階
  15. 15. © 2020 NTT DATA Corporation JVMTIで Hello World
  16. 16. © 2020 NTT DATA Corporation 16 概要 • エージェントの開始/終了時にコンソールにメッセージを出す • 動的アタッチにも対応する • オプション文字列に”error”が与えられたとき、エラーにする • サンプルソース • https://github.com/YaSuenag/jvmti-examples/tree/master/helloworld エージェント ロード エージェント アンロード エージェント アタッチ
  17. 17. © 2020 NTT DATA Corporation 17 エージェント開始時 ~プロセス起動時~ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved){ printf("Hello World from Agent_OnLoad()n"); return is_error(options) ? JNI_ERR : JNI_OK; } エクスポート用 マクロ 呼び出し規約 コールバック関数 ゼロで成功、ゼロ以外で失敗 オプション文字列 • エージェント開始のフックはAgent_OnLoad() • JVMTIエージェントに存在しなければならない関数 • ゼロ以外を返すと処理失敗とみなされ、JVMが起動しない
  18. 18. © 2020 NTT DATA Corporation 18 エージェント開始時 ~動的アタッチ~ JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved){ printf("Hello World from Agent_OnAttach()n"); return is_error(options) ? JNI_ERR : JNI_OK; } コールバック関数 • 途中から仕掛ける場合のフックはAgent_OnAttach() • 必須ではないが、動的アタッチに対応するなら必要な関数 • ゼロ以外を返すと処理失敗とみなされ、エージェントが起動しない • Agent_OnAttach()と違い、JVMは異常終了しない
  19. 19. © 2020 NTT DATA Corporation 19 エージェント終了時 JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm){ printf("Goodbye World from Agent_OnUnload()n"); } • エージェント終了のフックはAgent_OnUnload() • JVMTIエージェントに存在しなくてもよい関数 • JVMの終了後に呼び出されることに注意! • ライブ段階を抜けているため、ほとんどのJVMTI関数が使えない コールバック関数
  20. 20. © 2020 NTT DATA Corporation 20 ビルド cmake_minimum_required(VERSION 3.0) project(HelloWorld C) find_package(JNI REQUIRED) find_package(Threads REQUIRED) include_directories(${JNI_INCLUDE_DIRS}) add_library(helloworld SHARED helloworld.c) target_link_libraries(helloworld PRIVATE Threads::Threads) • 作り次第ではクロスプラットフォームも可能 • マルチスレッド対応の共有ライブラリとしてビルドする • JVMがマルチスレッド動作するため
  21. 21. © 2020 NTT DATA Corporation 21 実行 $ java -agentpath:/path/to/libhelloworld.so --version Hello World from Agent_OnLoad() openjdk 14.0.1 2020-04-14 OpenJDK Runtime Environment (build 14.0.1+7-Ubuntu-1ubuntu1) OpenJDK 64-Bit Server VM (build 14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing) Goodbye World from Agent_OnUnload() $ export LD_LIBRARY_PATH=`pwd` $ java -agentlib:helloworld --version Hello World from Agent_OnLoad() openjdk 14.0.1 2020-04-14 OpenJDK Runtime Environment (build 14.0.1+7-Ubuntu-1ubuntu1) OpenJDK 64-Bit Server VM (build 14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing) Goodbye World from Agent_OnUnload() フルパス指定 ライブラリ名指定 (要LD_LIBRARY_PARH)
  22. 22. © 2020 NTT DATA Corporation 22 動的アタッチ $ jcmd 2875 JVMTI.agent_load /path/to/libhelloworld.so 2875: return code: 0 • JVMTI.agent_loadで動的にJVMTIエージェントをアタッチ • 引数でライブラリを指定する • ライブラリ指定はフルパスがオススメ
  23. 23. © 2020 NTT DATA Corporation 23 エラーの場合 $ java -agentpath:/path/to/libhelloworld.so=error --version Hello World from Agent_OnLoad() options = error Error occurred during initialization of VM agent library failed to init: /path/to/libhelloworld.so $ jcmd 48 JVMTI.agent_load /path/to/libhelloworld.so error 48: return code: -1 • 動的アタッチではプロセスは動作したまま • エージェントは開始しないもののライブラリはロードされたまま(JDK-8252657) -agentpath、-agentlibではプロセス異常終了
  24. 24. © 2020 NTT DATA Corporation はじめての イベントフック
  25. 25. © 2020 NTT DATA Corporation 25 概要 • OutOfMemoryErrorをフックし、コンソールに出力します • ResourceExhaustedイベントを使います • https://docs.oracle.com/javase/jp/14/docs/specs/jvmti.html#ResourceExhausted • サンプルソース • https://github.com/YaSuenag/jvmti-examples/tree/master/oomehook エージェント ロード エージェント アンロード ResourceExhausted エージェント アタッチ
  26. 26. © 2020 NTT DATA Corporation 26 コードの流れ ① JVMTI環境を得る ② 権限を設定する ③ イベントコールバックを設定する ④ イベントを有効化する
  27. 27. © 2020 NTT DATA Corporation 27 ①JVMTI環境を得る • JavaVM::GetEnv()の仕様にJVMTIバージョンを与えて jvmtiEnv型のポインタを取得する • バージョン番号は使用する機能の導入されたバージョンから選択 jvmtiEnv *jvmti; vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION_1_1);
  28. 28. © 2020 NTT DATA Corporation 28 ②権限を設定する • 使用する関数やイベントで必要な権限を追加する • 設定可能な権限はJVM実装や実行段階によって異なる • 現在の権限の取得はGetCapabilities() jvmtiCapabilities capabilities = {0}; capabilities.can_generate_resource_exhaustion_heap_events = 1; jvmti->AddCapabilities(&capabilities);
  29. 29. © 2020 NTT DATA Corporation 29 ③イベントコールバックを設定する • コールバック関数を追加する • 1つのJVMTI環境につきコールバックは1つしか設定できない • 例:ResourceExhaustedは1環境に1つだけ設定可能 jvmtiEventCallbacks callbacks = {0}; callbacks.ResourceExhausted = &OnOutOfMemoryError; jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
  30. 30. © 2020 NTT DATA Corporation 30 ④イベントを有効化する • すべてのイベントはデフォルトで無効になっている • JVMTIドキュメントの「有効化」(Enabling)を参照 jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_RESOURCE_EXHAUSTED, NULL);
  31. 31. © 2020 NTT DATA Corporation 31 実行例 $ ./run.sh /path/to/liboomehook.so from JVMTI: OutOfMemoryError occurred: Java heap space Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at OOME.main(OOME.java:7)
  32. 32. © 2020 NTT DATA Corporation 応用編: 自家製GCログ
  33. 33. © 2020 NTT DATA Corporation 33 概要 • アプリケーション停止を伴うGCをフックし、コンソールに出力します • GarbageCollection{Start,Finish}イベントを使います • https://docs.oracle.com/javase/jp/14/docs/specs/jvmti.html#GarbageCollectionStart • https://docs.oracle.com/javase/jp/14/docs/specs/jvmti.html#GarbageCollectionFinish • サンプルソース • https://github.com/YaSuenag/jvmti-examples/tree/master/gchook エージェント ロード エージェント アンロード GarbageCollectionFinish エージェント アタッチ GarbageCollectionStart
  34. 34. © 2020 NTT DATA Corporation 34 GCイベントのワナ GarbageCollectionFinishGarbageCollectionStart アプリケーションスレッド停止区間 アプリケーション停止中にイベント発生!
  35. 35. © 2020 NTT DATA Corporation 35 実験:GC開始イベントで10秒スリープさせる void JNICALL OnGarbageCollectionStart(jvmtiEnv *jvmti){ std::cout << "from JVMTI: GC start" << std::endl; if(need_to_suspend){ std::cout << "from JVMTI: Sleep 10 secs in GarbageCollectionStart()...“ << std::endl; sleep(10); std::cout << "from JVMTI: Resume from sleep" << std::endl; } }
  36. 36. © 2020 NTT DATA Corporation 36 実行例 [0.026s][info][safepoint] Entering safepoint region: G1CollectFull from JVMTI: GC start from JVMTI: Sleep 10 secs in GarbageCollectionStart()... from JVMTI: Resume from sleep [10.029s][info][gc ] GC(0) Pause Full (System.gc()) 1M->0M(10M) 2.236ms from JVMTI: GC finish [10.029s][info][safepoint] Leaving safepoint region [10.029s][info][safepoint] Total time for which application threads were stopped: 10.0037798 seconds, Stopping threads took: 0.0000045 seconds • GC停止:2.2ミリ秒 • アプリケーション停止:10秒 ?
  37. 37. © 2020 NTT DATA Corporation 37 見る場所によって異なる”停止時間” 停止要求 アプリケーションスレッド停止 GarbageCollectionStart GC GarbageCollectionFinish アプリケーション再開 GCによる停止時間 アプリケーションの停止時間 GCチューニングをどんなにガンバっても JVMTIエージェントがそれを台無しにすることもある!
  38. 38. © 2020 NTT DATA Corporation 38 では、どうすればいいか? 別スレッドで動かす!
  39. 39. © 2020 NTT DATA Corporation 39 GCイベントとマルチスレッド GCワーカー JVMTIのスレッド 停止要求 アプリケーションスレッド停止 GarbageCollectionStart GC GarbageCollectionFinish アプリケーション再開 GCによる停止時間 アプリケーションの停止時間 ロック通知待ち… 処理
  40. 40. © 2020 NTT DATA Corporation 40 注意 • GCワーカーと並行して動く • アプリケーション完全停止中でも動き回れる • Javaヒープを触った瞬間にGCによる完全停止に巻き込まれる! jvmti->RawMonitorWait(monitor, 0L); // GCイベントからの通知待ち std::cout << "from JVMTI agent thread" << std::endl; env->NewStringUTF(“Access Java heap”); // 新しい文字列を作る std::cout << "from JVMTI agent thread: continue" << std::endl;
  41. 41. © 2020 NTT DATA Corporation まとめ
  42. 42. © 2020 NTT DATA Corporation 42 まとめ • JVMTI:デバッグや監視に使えるイベントドリブンな共有ライブラリ • イベントの種類やイベントハンドラの書き方によっては アプリケーション性能へ甚大な影響を与えるため注意が必要 • 必要なイベントを見極めて、可能な限り軽量なロジックを作る!
  43. 43. © 2020 NTT DATA Corporation本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です

×