Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

531 views

Published on

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

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

Published in: Technology
  • Be the first to comment

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本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です

×