JVMTI
Javaのコードを書き換えることなく、実行中にスレッドやヒープの情報を取得するツールはいろいろと存在する。これらのツールのようにJavaの実行環境情報を実行中に取得するにはどうしたらいいんだろう。
JNIでクラス検索するときのパッケージ名ではJNIを使って、CからJVMを起動したけど、今度はJVMTIを使って、JVMから呼び出されるCのコードを作ってみよう。
JVITI(JVM Tool Interface)
https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html
※今回試した実行環境はJDK8, openSUSE 42.2
Java側のサンプル
まずは監視対象となるJavaのサンプルを用意。ただのHello world。local/Main.java
package local; class Main { public static void main(String ... args) { System.out.println("Hello, JVMTI in Java."); } }
エージェント側
JVMTIではJVMから呼び出される共有ライブラリとして作成する必要がある。この共有ライブラリをJava起動時にエージェントとして指定すれば、JVMから呼び出してくれる。
jvmtitest.c
#include <stdio.h> #include <string.h> #include <jvmti.h> static jvmtiEnv *jvmti = 0; static void JNICALL ClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jclass klass); static jstring getClassName(JNIEnv *env, jclass klass); JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION); jvmtiEventCallbacks callbackTable = { 0 }; callbackTable.ClassPrepare = ClassPrepare; (*jvmti)->SetEventCallbacks(jvmti, &callbackTable, sizeof(callbackTable)); (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); printf("agent is loading...\n"); return 0; } JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) { (*jvmti)->DisposeEnvironment(jvmti); printf("agent is unloading...\n"); } static void JNICALL ClassPrepare(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jclass klass) { jstring strObj = getClassName(jni_env, klass); const char *str = (*jni_env)->GetStringUTFChars(jni_env, strObj, NULL); if (strcmp(str, "local.Main") == 0) { printf("local.Main prepared.\n"); } (*jni_env)->ReleaseStringUTFChars(jni_env, strObj, str); } static jstring getClassName(JNIEnv *env, jclass klass) { jclass cls = (*env)->GetObjectClass(env, klass); jmethodID mid = (*env)->GetMethodID(env, cls, "getName", "()Ljava/lang/String;"); jstring strObj = (jstring)(*env)->CallObjectMethod(env, klass, mid); return strObj; }
基本的なスタイルとしてはAgent_OnLoad,Agent_OnUnload関数を作成しておくと、JVMTIエージェントのロード時、アンロード時に呼び出してくれるため、このタイミングでイベントハンドラを登録することになる。
今回の場合はクラスの準備ができたときに呼び出されるClassPrepareを登録している。
これで、各クラスの準備ができる度にClassPrepareイベントのハンドラが呼び出されるため、クラス名を取得して、クラス名が"local.Main"の場合のみ、printfで出力するようにした。
また、JNIのJNIEnvと同様にJVMTIの関数はjvmtiEnvを通して参照するため、Agent_OnLoad時にJVMTIEnvを初期化しておく必要がある。
コンパイル&実行
これをコンパイルして、実行すると、以下のように出力される。JVMTIのヘッダファイルをインクルードする必要があるため、JDKのインストール先のincludeと環境依存のinclude/xxxをインクルードパスに追加しておこう。
$ javac local/Main.java $ gcc -Wall -std=c99 -shared -fPIC -D_REENTRANT -o libjvmtitest.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux jvmtitest.c $ java -agentpath:./libjvmtitest.so local.Main agent is loading... local.Main prepared. Hello, JVMTI in Java. agent is unloading...
- エージェントのロード
- local.Mainクラス準備イベント発生
- mainメソッド実行
- エージェントのアンロード
0 件のコメント:
コメントを投稿