Java 本机界面偷偷摸摸的分叉行为

Java Native Interface sneaky forking behavior

经过 非常 长时间的搜索和相关错误,我发现了这个奇怪的行为:

如果在 Linux 我 运行 一个单一的 JNI 方法做一个 select:

JNIEXPORT void JNICALL Java_SelectJNI_select(JNIEnv *env, jobject thisObj) {
  // Print the curerent PID
  fprintf(stderr, "PID: %d\n", getpid());

  // Wait for 30 seconds
  struct timeval *timeout = (struct timeval *) calloc(1, sizeof(struct timeval));
  timeout->tv_sec = 30;
  timeout->tv_usec = 0;
  select(0, NULL, NULL, NULL, timeout);

  return;
}

然后我 运行 带有 strace 的可执行文件, select 不是用我打印的 PID 执行的,而是用 child 的 PID,用原来的 object 实际上在等待一个互斥量(如果我在一个普通的小型 C 程序中执行相同的调用,则不会发生这种情况)。

strace -f -o strace_output.txt java SelectJNI 打印:

PID: 46811 

然后 grep select\( strace_output.txt 将 return:

46812 select(0, NULL, NULL, NULL, {tv_sec=30, tv_usec=0} <unfinished ...>

我的猜测是 JNI 正在分叉,以某种方式用它自己的包装版本 替换原来的 select,可能是为了保持响应。

我有很多个问题,但我更关心的是:

  1. 我的假设是否正确? JNI替代我脚下的功能?
  2. 是否在某处记录了此行为?
  3. 调用实际 select 的过程似乎总是第一个 child 的过程。我可以依靠它吗?如果不是,我如何找出 select 实际上是 运行ning?

JVM 确实可以分叉,但这样做是为了创建新的 JVM 线程,而不是整个进程。虽然 46811 是 PID,但实际上 运行 您的相关代码的线程具有 TID 46812(这是 strace 打印的内容),同时仍然 运行 在 PID 下46811. 将示例中的 getpid 替换为 gettid 应该会导致一致的输出。

我想详细说明@nanofarad 接受的答案,并明确解决我自己问题的 3 点。

My guess is that JNI is forking and, in some way replacing the original select with its own wrapped version, probably to remain responsive. [...]

  1. Is my hypothesis correct? JNI replacing functions under my feet?

不,不是。

JNI执行的select没有什么特别之处

JNI 将其替换为“进程分叉的东西”的假设是错误的:我只是将 strace 打印的 TID 误解为 PID。

JNI 只是在 Java 线程中执行 strace。

  1. Is this behavior documented somewhere?

不需要:因为 JNI 调用是在调用 Java 线程中执行的,所以没有什么可写的。

  1. The process where the actual select is invoked seems always to be that of the first child (et cetera...)

这是第一个派生线程的 TID,它似乎总是等于 PID + 1,但我是一个可能的行为(Java 线程是在运行时启动后立即创建的),它不是一定会的。