Java Native Interface偷偷摸摸的分叉行为



very经过长时间的搜索和寻找一个相关的bug,我发现了这个奇怪的行为:

如果在Linux上我运行一个JNI方法来做一个select:

JNIEXPORT void JNICALL Java_SelectJNI_select(JNIEnv *env, jobject thisObj) {
// Print the curerent PID
fprintf(stderr, "PID: %dn", 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执行,而是用子对象的PID执行,原始对象实际上在等待互斥锁(如果我在普通的小C程序中执行相同的调用,则不会发生这种情况)。

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

PID: 46811 

grep select( strace_output.txt将返回:

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

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

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

  1. 我的假设正确吗?JNI取代我脚下的功能?
  2. 这个行为是否被记录在某个地方?
  3. 实际调用select的进程似乎总是第一个子进程。我能相信吗?如果没有,我如何找出select实际运行的位置?

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

我想详细说明@nanofarad的公认答案,并明确指出我自己的问题的三个要点。

我的猜测是JNI正在分叉,并以某种方式取代原始选择和它自己的包装版本,可能会保留响应。[…]

  1. 我的假设正确吗?JNI取代我脚下的功能?

不,它不是。

JNI执行的select没有什么特别的。

假设JNI正在用"分叉过程的东西"取代它;是错误的:我只是误解了strace打印的TID为PID。

JNI只是在Java线程中执行字符串。

  1. 此行为是否记录在某处?

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

  1. 实际调用select的进程似乎总是第一个子进程(等等…)

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

最新更新