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

Posted

技术标签:

【中文标题】Java Native Interface 偷偷摸摸的分叉行为【英文标题】:Java Native Interface sneaky forking behavior 【发布时间】:2021-02-09 15:29:02 【问题描述】:

非常 长时间的寻找和一个相关的错误之后,我遇到了这种奇怪的行为:

如果在 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 执行的,而是使用孩子的 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 正在分叉,并且在某种程度上用自己的包装版本替换了原始选择,可能是为了保持响应。

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

    我的假设正确吗? JNI 替换我脚下的函数? 此行为是否记录在某处? 调用实际选择的进程似乎总是第一个子进程。我可以依靠吗?如果没有,我如何找出select 实际运行的位置?

【问题讨论】:

您是否确认父级没有立即分叉以设置 JVM 中预期的许多线程? 我可能没听懂你的话,但我不认为是这样的:如果父母已经分叉了,那么printf报告的pid和in strace 将是相同的。 令我惊讶的是,该进程似乎在 fprintf 之后分叉或委托选择调用 ,即调用 select 时。不过,也许我没听懂你的话。 如果我没记错的话,strace 输出中的 46812 是 TID,而不是 PID。而是打印gettid 的结果。 @Rick77 跑题了,但你可以用grep select\( 代替strace -f -e select ... 【参考方案1】:

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

【讨论】:

谢谢@nanofarad,我可以在打印gettid() 结果后确认是这种情况。不过,我有点惊讶(在一个更复杂的示例中)我能够通过使用 TID 而不是 PID 向进程发送 SIGINT:“kill”是否也适用于 TID? @Rick77 可以使用进程中的任何 TID (ref) 将信号路由到 进程,但哪个线程处理信号取决于信号是每个进程、每个线程等(请参阅链接参考)【参考方案2】:

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

我的猜测是 JNI 正在分叉,并且在某种程度上取代了 带有自己包装版本的原始选择,可能会保留 反应灵敏。 [...]

    我的假设正确吗? JNI 替换我脚下的函数?

不,不是。

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

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

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

    此行为是否记录在某处?

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

    调用实际选择的进程似乎总是第一个子进程的进程(等等...)

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

【讨论】:

以上是关于Java Native Interface 偷偷摸摸的分叉行为的主要内容,如果未能解决你的问题,请参考以下文章

职场救星: 用Python如何实现办公效率化,挤出时间去偷偷摸鱼

职场救星: 用Python如何实现办公效率化,挤出时间去偷偷摸鱼

+Java中的native关键字浅析(Java+Native+Interface)++

Java固有接口JNI(Java Native Interface)之HelloWorld

Java Programming Tutorial Java Native Interface (JNI)

Mac下Java JNI (java native interface)调C