目的
JNIなどのnative codeの呼び出し元を調査するのは、Javaのデバッグ機能ではできません。今回はその方法を試します。
OpenJ9のJavaVM(IBM Java, Semeru Runtime)、以下の環境で試しました。
$ java -version
openjdk version "1.8.0_432"
IBM Semeru Runtime Open Edition (build 1.8.0_432-b06)
Eclipse OpenJ9 VM (build openj9-0.48.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20241107_1079 (JIT enabled, AOT enabled)
OpenJ9 - 1d5831436e
OMR - d10a4d553
JCL - 306866566f based on jdk8u432-b06)
ソースコード
javacore, heapdump を取得するコードでJNIの実行を確認します。
package pdprof.dump;
import com.ibm.jvm.Dump;
public class Main {
public static void main(String[] args) {
try {
// Javacore(スレッドダンプ)を取得
System.out.println("Taking Javacore...");
Dump.JavaDump();
Thread.sleep(10000);
// Heapdumpを取得
System.out.println("Taking Heapdump...");
Dump.HeapDump();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Thread.sleep(10000); は あとで gcore の実行を試すのに追加してあります。
コンパイル
パッケージのトップディレクトリで以下を実行します。(IDEでコンパイルしても良いです)
$ javac pdprof/dump/Main.java
実行
オプションなしで実行します。
$ java pdprof.dump.Main
Taking Javacore...
JVMDUMP034I User requested Java dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260330.231503.7289.0001.txt' through com.ibm.jvm.Dump.JavaDump
JVMDUMP010I Java dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260330.231503.7289.0001.txt
Taking Heapdump...
JVMDUMP034I User requested Heap dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260330.231503.7289.0002.phd' through com.ibm.jvm.Dump.HeapDump
JVMDUMP010I Heap dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260330.231503.7289.0002.phd
JNIのコードが含まれているライブラリを探す
ここでは、Java_com_ibm_jvm_Dump_JavaDumpImpl の JNIが含まれているライブラリを探します。探したい関数がある場合は文字列を置き換えます。
nm コマンドが使えます。
$ for i in `find /opt/ibm/java-x86_64-80/ -name "*.so*"`; do echo "*** $i ***"; nm -D $i | grep Java_com_ibm_jvm_Dump_JavaDumpImpl; done
...
*** /opt/ibm/java-x86_64-80/jre/lib/amd64/compressedrefs/libj9zlib29.so ***
*** /opt/ibm/java-x86_64-80/jre/lib/amd64/compressedrefs/libjclse29.so ***
00000000000138d0 T Java_com_ibm_jvm_Dump_JavaDumpImpl
*** /opt/ibm/java-x86_64-80/jre/lib/amd64/compressedrefs/libjsig.so ***
...
*** /opt/ibm/java-x86_64-80/jre/lib/amd64/default/libjclse29.so ***
00000000000138d0 T Java_com_ibm_jvm_Dump_JavaDumpImpl
*** /opt/ibm/java-x86_64-80/jre/lib/amd64/default/libjsig.so ***
...
libjclse29.so に含まれている事が分かりました。
bpftrace でログをとる
bpftrace を使います。導入していない場合は以下のコマンドで導入します(CentOS)
$ sudo dnf -y install bpftrace
bpftrace を実行します。
$ sudo bpftrace -e 'uprobe:/opt/ibm/java-x86_64-80/jre/lib/amd64/compressedrefs/libjclse29.so:Java_com_ibm_jvm_Dump_JavaDumpImpl { printf("PID: %d, Comm: %s called JavaDumpImpl\n\nCall stack is %s", pid, comm, ustack); }' &
実行中に先ほどのJavaプログラムを実行するとJNIが呼び出されたことを検知できました。。
$ java pdprof.dump.Main
Taking Javacore...
JVMDUMP034I User requested Java dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260330.234446.43684.0001.txt' through com.ibm.jvm.Dump.JavaDump
PID: 43684, Comm: main called JavaDumpImpl
Call stack is
Java_com_ibm_jvm_Dump_JavaDumpImpl+0
0x19b18
JVMDUMP010I Java dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260330.234446.43684.0001.txt
Taking Heapdump...
JVMDUMP034I User requested Heap dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260330.234446.43684.0002.phd' through com.ibm.jvm.Dump.HeapDump
JVMDUMP010I Heap dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260330.234446.43684.0002.phd
bpftrace でコマンドを実行する
呼び出されたタイミングでコマンドを実行して資料収集などができます。
以下は gcore を実行する例です。
$ sudo bpftrace -e '
uprobe:/opt/ibm/java-x86_64-80/jre/lib/amd64/compressedrefs/libj9zlib29.so:Java_com_ibm_jvm_Dump_JavaDumpImpl {
printf("JavaDump detected! Executing script...\n");
system("/usr/bin/gcore %d", pid);
}' &
gcore 実行よりも前にプロセスが終了してしまうと、gcoreの実行が失敗してしまいます。ここでは javacore 出力のJNIを対象にしましたが、そうでない場合は kill -3 %d を実行するのも良いかと思います。
java pdprof.dump.Main
Taking Javacore...
JavaDump detected! Executing script...
JVMDUMP034I User requested Java dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260331.000303.45360.0001.txt' through com.ibm.jvm.Dump.JavaDump
JVMDUMP010I Java dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/javacore.20260331.000303.45360.0001.txt
[New LWP 45384]
[New LWP 45383]
[New LWP 45381]
[New LWP 45380]
[New LWP 45379]
[New LWP 45378]
[New LWP 45377]
[New LWP 45376]
[New LWP 45375]
[New LWP 45374]
[New LWP 45373]
[New LWP 45372]
[New LWP 45371]
[New LWP 45370]
[New LWP 45369]
[New LWP 45368]
[New LWP 45367]
[New LWP 45366]
[New LWP 45365]
[New LWP 45364]
[New LWP 45363]
[New LWP 45362]
[New LWP 45361]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f03baa883aa in __futex_abstimed_wait_common () from /lib64/libc.so.6
warning: Memory read failed for corefile section, 4096 bytes at 0xffffffffff600000.
Saved corefile core.45360
[Inferior 1 (process 45360) detached]
Taking Heapdump...
JVMDUMP034I User requested Heap dump using '/home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260331.000313.45360.0002.phd' through com.ibm.jvm.Dump.HeapDump
JVMDUMP010I Heap dump written to /home/keniooi/eclipse/JniTest/DumpTest/src/heapdump.20260331.000313.45360.0002.phd
まとめ
今回は Liberty や docker は使いませんでしたが、JNIなどのライブラリに含まれる関数を見つけて使用状況をモニターする方法を確認しました。
この様な方法があることを覚えておくといつか使えるかもしれません。