Java JNI GetPrimitiveArrayCritical导致JVM崩溃
我有一个java程序调用JNI函数对字节数组执行计算,返回一个长数组。这些程序在Oracle JDK 1.8.0221上运行。为了测试吞吐量,我使用JMH1.21在大小为64M的同一字节数组上多次运行代码 java端代码如下所示Java JNI GetPrimitiveArrayCritical导致JVM崩溃,java,java-native-interface,jvm-hotspot,jvm-crash,Java,Java Native Interface,Jvm Hotspot,Jvm Crash,我有一个java程序调用JNI函数对字节数组执行计算,返回一个长数组。这些程序在Oracle JDK 1.8.0221上运行。为了测试吞吐量,我使用JMH1.21在大小为64M的同一字节数组上多次运行代码 java端代码如下所示 public long[] compute(byte[] input, int resultSize) { long[] result = new long[resultSize]; nativeCompute(input, result); r
public long[] compute(byte[] input, int resultSize) {
long[] result = new long[resultSize];
nativeCompute(input, result);
return result;
}
native void nativeCompute(byte[] input, long[] output);
JNI代码只是从Java数组中获取指针并将其传递给计算函数
JNIEXPORT void JNICALL Java_NativeCompute_nativeCompute
(JNIEnv *env, jobject self, jbyteArray input, jlongArray result) {
// uint8_t *data = (uint8_t *) env->GetPrimitiveArrayCritical(input, 0);
// uint64_t *localres = (uint64_t *) env->GetPrimitiveArrayCritical(result, 0);
jbyte *data = env->GetByteArrayElements(input, 0);
jlong *localres = env->GetLongArrayElements(result, 0);
compute((uint8_t *) data, (uint64_t *) localres);
// env->ReleasePrimitiveArrayCritical(input, data, JNI_ABORT);
// env->ReleasePrimitiveArrayCritical(result, localres, JNI_COMMIT);
env->ReleaseLongArrayElements(result, localres, JNI_COMMIT);
env->ReleaseByteArrayElements(input, data, JNI_ABORT);
}
我使用GetByteArrayElements
和GetLongArrayElements
从java数组中获取指针,程序运行良好
为了避免数组复制,我尝试切换到GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical
,这可以从注释掉的代码中看出。性能提高了15%,但JVM在运行了一些(30-150)迭代后崩溃。错误日志如下所示
# Run progress: 0.00% complete, ETA 00:12:30
# Fork: 1 of 5
# Warmup Iteration 1: 7.436 ops/s
# Warmup Iteration 2: #
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fe4b14ca554, pid=14023, tid=0x00007fe49baa2700
#
# JRE version: Java(TM) SE Runtime Environment (8.0_221-b11) (build 1.8.0_221-b11)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# V [libjvm.so+0x987554][thread 140620183496448 also had an error]
[thread 140619839837952 also had an error]
oopDesc* PSPromotionManager::copy_to_survivor_space<false>(oopDesc*)+0x204
#
#运行进度:完成0.00%,预计到达时间00:12:30
#叉子:5个中的1个
#预热迭代1:7.436次/秒
#预热迭代2:#
#Java运行时环境检测到一个致命错误:
#
#pc=0x00007fe4b14ca554、pid=14023、tid=0x00007fe49baa2700时的SIGSEGV(0xb)
#
#JRE版本:Java(TM)SE运行时环境(8.0_221-b11)(build 1.8.0_221-b11)
#Java虚拟机:Java热点(TM)64位服务器虚拟机(25.221-b11混合模式linux-amd64压缩oops)
#有问题的框架:
#V[libjvm.so+0x987554][线程140620183496448也有错误]
[线程140619839837952也有错误]
oopDesc*PSPromotionManager::将_复制到_幸存者_空间(oopDesc*)+0x204
#
当我切换回GetArrayElements
时,这个错误消失了。但我仍然希望从GetPrimitiveArrayCritical
中获得15%的性能改进
如有任何建议,我们将不胜感激。谢谢
更新日期:2019年8月20日
谢谢大家的建议!我已经按照你的建议试过了,这里有一些更新
虽然还不清楚为什么错误只发生在JMH环境中,但我认为我们已经找到了根本原因,感谢大家的帮助 在我看来,你的电脑里有些可疑的东西。如果我使用与您相同的方法进行非常简单的测试,我不会遇到任何问题 另外,非常重要的是,
compute
不做任何与JNI相关的事情PrimitiveArray
附带价格
当然,对于同一函数上的100个循环(如此处:),您可以看到加速带来的明显好处
Access via PrimitiveArray
14.64 real 14.55 user 0.12 sys
Access via ArrayElements
51.55 real 35.94 user 15.13 sys
快两倍半。无论如何,由于PrimitiveArray
是有成本的
调用GetPrimitiveArrayCritical后,本机代码
在调用之前,不应运行较长时间
ReleasePrimitiveArrayCritical
我会重新思考,对于这种数组大小,是否真正需要使用PrimitiveArray
更新
Ups!在我准备答案时,问题似乎已经解决了;) 一,。您应该检查
GetPrimitiveArrayCritical
返回的指针是否为NULL
。如果是这种情况,则VM内存不足。2.当您注释掉对compute
的内部调用时,它是否有效?(只是为了确保错误不是由compute
函数引起的…)可能与此无关,但您永远不知道:PrimitiveArrayCritical案例以不同的顺序释放数组。您也可以使用ByteBuffer
而不是byte[]
来避免复制,因为JNI具有访问备份阵列的特定功能。谢谢。compute函数目前需要稳定的100毫秒来处理64MB的数据,所以我想现在切换到ByteBuffer并不紧急。但我会认真考虑的。