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正在分叉,并且以某种方式用自己的包装版本替换原始选择,可能是为了保持响应。
我有很多问题,但我更关心的是:
- 我的假设正确吗?JNI取代我脚下的功能?
- 这个行为是否被记录在某个地方?
- 实际调用select的进程似乎总是第一个子进程。我能相信吗?如果没有,我如何找出
select
实际运行的位置?
JVM确实可能分叉,但它这样做是为了创建新的JVM线程,而不是整个进程。虽然46811是PID,但实际运行代码的线程的TID为46812(这是strace打印的内容),同时仍然在PID 46811下运行。将样本中的getpid
替换为gettid
应该会导致一致的输出。
我想详细说明@nanofarad的公认答案,并明确指出我自己的问题的三个要点。
我的猜测是JNI正在分叉,并以某种方式取代原始选择和它自己的包装版本,可能会保留响应。[…]
- 我的假设正确吗?JNI取代我脚下的功能?
不,它不是。
JNI执行的select
没有什么特别的。
假设JNI正在用"分叉过程的东西"取代它;是错误的:我只是误解了strace
打印的TID为PID。
JNI只是在Java线程中执行字符串。
- 此行为是否记录在某处?
不需要:因为JNI调用是在调用的Java线程中执行的,所以没有什么可写的。
- 实际调用select的进程似乎总是第一个子进程(等等…)
第一个生成的线程的TID似乎总是等于PID + 1,但这是一种可能的行为(Java线程是在运行时启动后创建的),它并不一定是。