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,可能是为了保持响应。
我有很多个问题,但我更关心的是:
- 我的假设是否正确? JNI替代我脚下的功能?
- 是否在某处记录了此行为?
- 调用实际 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.
[...]
- Is my hypothesis correct? JNI replacing functions under my feet?
不,不是。
JNI执行的select
没有什么特别之处
JNI 将其替换为“进程分叉的东西”的假设是错误的:我只是将 strace
打印的 TID 误解为 PID。
JNI 只是在 Java 线程中执行 strace。
- Is this behavior documented somewhere?
不需要:因为 JNI 调用是在调用 Java 线程中执行的,所以没有什么可写的。
- The process where the actual select is invoked seems always to be that of the first child (et cetera...)
这是第一个派生线程的 TID,它似乎总是等于 PID + 1,但我是一个可能的行为(Java 线程是在运行时启动后立即创建的),它不是一定会的。
经过 非常 长时间的搜索和相关错误,我发现了这个奇怪的行为:
如果在 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,可能是为了保持响应。
我有很多个问题,但我更关心的是:
- 我的假设是否正确? JNI替代我脚下的功能?
- 是否在某处记录了此行为?
- 调用实际 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. [...]
- Is my hypothesis correct? JNI replacing functions under my feet?
不,不是。
JNI执行的select
没有什么特别之处
JNI 将其替换为“进程分叉的东西”的假设是错误的:我只是将 strace
打印的 TID 误解为 PID。
JNI 只是在 Java 线程中执行 strace。
- Is this behavior documented somewhere?
不需要:因为 JNI 调用是在调用 Java 线程中执行的,所以没有什么可写的。
- The process where the actual select is invoked seems always to be that of the first child (et cetera...)
这是第一个派生线程的 TID,它似乎总是等于 PID + 1,但我是一个可能的行为(Java 线程是在运行时启动后立即创建的),它不是一定会的。