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

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

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

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

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

如果有人能阐明这一点,那就太好了。提前致谢。

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

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的方式* 例如

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

最佳答案

经过几天的研究这个JNI问题,我们终于找到了原因,我想在这里分享我们的经验,希望对其他人有帮助。

首先,我们首先需要使用 JNI 的原因是因为我们需要使用 Linux native 库的第 3 方网络库,不幸的是,这就是我们问题的原因。

该库为我们提供了一个回调句柄,我们实现它来接收来自它的传入网络消息,而我们后来发现,这个回调只是一个信号处理程序。因此,这意味着每当信号弹出时都会调用此信号处理程序,甚至在 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 线程。

我们看到的 gdb 堆栈跟踪基本上是当一个 gc 线程实际上正在堆上做一些工作,然后从我们的应用程序收到一个调用来执行一些应用程序工作,然后是一些 JNI API 时发生的情况来电...BOOM

解决方案:

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

附:也许有些细节并不完全准确,所以欢迎任何 JVM 专家的建议。我会尽力按照建议纠正它们。

谢谢

更新1(@apangin): 我们这里有另一个 gdb 堆栈跟踪。只是想知道 #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

关于java - gc 期间调用 JNI 函数时 JVM 崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44062332/

相关文章:

node.js - Nodejs Websocket内存泄漏

java - Android Studio Robolectric 单元测试 - 默认值无效

java - Aspectj 可选参数绑定(bind)

java - 在 fragment 上加载图像,android studio

objective-c - UITextField 在类型上崩溃

c# - GC 何时可以收集挂接到事件的 Lambda?

java - 运行此 Java 程序时 RAM 迅速增加

java - 如何在Hibernate框架中正确生成xml

java - 如何在Android中调用另一个类的一个方法

android - app-debug.apk 在我的 Android 设备上手动安装时崩溃