在gc期间调用JNI函数时JVM崩溃

时间:2017-05-19 05:50:21

标签: java crash garbage-collection jvm java-native-interface

我们有一个Java应用程序,它有一个多线程(pthread)的JNI层,并将根据从底层网络收到的消息回调到Java级别。

我们注意到每次崩溃都是由gc引起的。我们甚至可以通过在JNI层从网络接收消息时通过调用jmap -histo <pid>来手动触发gc来模拟这种崩溃。

鉴于我们在这篇文章https://stackoverflow.com/a/39401467/4523221中读到了关于GC期间JVM行为的信息,我们仍然无法弄清楚为什么这种崩溃与gc有关,因为在gc期间JNI函数调用被阻止了。

如果有人能说清楚这一点,那就太好了。提前谢谢。

以下是我们在应用程序崩溃后收集的堆栈跟踪。

Program terminated with signal 6, Aborted.
#0  0x0000003cdce325e5 in raise () from /lib64/libc.so.6
#1  0x0000003cdce33dc5 in abort () from /lib64/libc.so.6
#2  0x00007fdafe2516b5 in os::abort(bool) () from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#3  0x00007fdafe3efbf3 in VMError::report_and_die() ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#4  0x00007fdafde2f3e2 in report_vm_error(char const*, int, char const*, char const*) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#5  0x00007fdafe24c1ff in os::PlatformEvent::park() ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#6  0x00007fdafe20c538 in Monitor::ILock(Thread*) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#7  0x00007fdafe20c73f in Monitor::lock_without_safepoint_check() ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#8  0x00007fdafe2e7a1f in SafepointSynchronize::block(JavaThread*) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#9  0x00007fdafe39bcdd in JavaThread::check_safepoint_and_suspend_for_native_trans(JavaThread*) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#10 0x00007fdafe0123d8 in jni_NewByteArray ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#11 0x00007fdaa447b7d1 in JNIEnv_::NewByteArray (this=0x7fdaf800c9f8, len=7)
    at /usr/java/jdk1.8.0_65/include/jni.h:1643
---omitted---
#19 0x0000003cdd20b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#20 0x00007fdafe24c133 in os::PlatformEvent::park() ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#21 0x00007fdafe20ce27 in Monitor::IWait(Thread*, long) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#22 0x00007fdafe20d5f0 in Monitor::wait(bool, long, bool) ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
---Type <return> to continue, or q <return> to quit---
#23 0x00007fdafe39ed51 in Threads::destroy_vm() ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#24 0x00007fdafdfff931 in jni_DestroyJavaVM ()
   from /usr/java/jdk1.8.0_65/jre/lib/amd64/server/libjvm.so
#25 0x00007fdafe91a63d in JavaMain () from /usr/java/jdk1.8.0_65/bin/../lib/amd64/jli/libjli.so
#26 0x0000003cdd207aa1 in start_thread () from /lib64/libpthread.so.0
#27 0x0000003cdcee8aad in clone () from /lib64/libc.so.6

我们获得JNIEnv *的方式 e.g。

JNIEnv *env = 0;
jint result = jvm->GetEnv((void **) &env, JNI_VERSION_1_8);
if (result != JNI_OK) {
    result = jvm->AttachCurrentThread((void **) &env, NULL);

1 个答案:

答案 0 :(得分:6)

在花了几天调查这个JNI问题之后,我们终于找到了原因,我想在这里分享我们的经验,希望它能帮助其他人。

首先,我们首先需要使用JNI的原因是因为我们需要使用一个Linux本地库的第三方网络库,不幸的是,这是我们问题的原因。 / p>

库为我们提供了一个回调句柄,我们实现了它来接收来自它的传入网络消息,而这个回调,我们后来发现,它只是一个信号处理程序。因此,这意味着只要信号弹出,就会调用此信号处理程序,在gc期间甚至

由于C线程在JVM的安全点期间保持运行,如果那些C线程没有连接到JVM就没问题,否则灾难肯定会发生。

这是我们认为发生过的事情。 (以下所有内容都发生在JNI层中)

  1. 应用程序启动。我们初始化并缓存JNI资源,例如: Jmv *,方法ID等。
  2. 我们向库注册了一个C函数来接收消息。 C函数是一个函数,它可以调用JNI API来分配内存以容纳收到的消息并将它们传递给Java。之后,我们才开始等待收到的消息。
  3. 当消息最终到达时,上面提到的C函数被调用来处理消息,但是等待......这个线程处理回调是什么。那本来是主线程或嗯...任何可用的线程。
  4. 正如任何JNI教科书中所教导的那样,在调用任何JNI API之前,如果尚未这样做,我们首先将线程附加到JVM。 大!
  5. 现在,在GC期间,所有Java线程都被阻止,但C层仍在运行。在这个关键时刻,如果消息到达,则调用一些线程(任何线程)来处理消息。但在gc期间仍有哪些线程可用?所有应用程序线程都被阻止了,此时唯一仍在运行的(我们的猜测)不幸的是gc线程。
  6. 我们看到的gdb堆栈跟踪基本上就是当一个gc线程实际上正在堆上做一些工作然后从我们的应用程序调用一些应用程序工作然后一些JNI API时发生的事情来电...... BOOM

    <强>解决方案:

    • 有一个处理库回调的C线程
    • 永远不要将该线程附加到JVM
    • 让其他线程附加到JVM以执行Native-Java转换。

    P.S。也许有些细节并不完全准确,因此欢迎任何JVM专家建议。我会尽力纠正它们。

    由于

    Update.1(@apangin): 我们在这里有另一个gdb stacktrace。只是想知道#18的GangWorker是否是并行GC线程。

    #0  0x00000035b90325e5 in raise () from /lib64/libc.so.6
    #1  0x00000035b9033dc5 in abort () from /lib64/libc.so.6
    #2  0x00007febd60813b5 in os::abort(bool) () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #3  0x00007febd6223673 in VMError::report_and_die() () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #4  0x00007febd60868bf in JVM_handle_linux_signal () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #5  0x00007febd607ce13 in signalHandler(int, siginfo*, void*) () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #6  <signal handler called>
    #7  0x00007feb9fcf551c in JNIEnv_::NewByteArray (this=0x7febd001d9f8, len=8) at /usr/java/jdk1.8.0_131/include/jni.h:1643
    *<omitted app specific calls>*
    #13 <signal handler called>
    #14 0x00000035b980b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
    #15 0x00007febd607b7e3 in os::PlatformEvent::park() () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #16 0x00007febd603c037 in Monitor::IWait(Thread*, long) () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #17 0x00007febd603c956 in Monitor::wait(bool, long, bool) () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #18 0x00007febd6244d6b in GangWorker::loop() () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #19 0x00007febd6082568 in java_start(Thread*) () from /usr/java/jdk1.8.0_131/jre/lib/amd64/server/libjvm.so
    #20 0x00000035b9807aa1 in start_thread () from /lib64/libpthread.so.0
    #21 0x00000035b90e8aad in clone () from /lib64/libc.so.6