Java JNI GetPrimitiveArrayCritical导致JVM崩溃

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

我有一个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);
    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日 谢谢大家的建议!我已经按照你的建议试过了,这里有一些更新

  • 我添加了检查指针是否为NULL的代码。它们不是空的

  • 该错误仅在JMH环境中发生。当我独立运行代码并重复2000次迭代时,没有错误

  • 我注释掉了计算结果,错误消失了。看起来崩溃是由数组溢出引起的。我仔细检查了代码,发现 当输入元素的数量是64的倍数时,我的代码将意外地跨越结果数组的边界。我修复了那个bug,现在错误消失了。我应该仔细检查代码以避免这种愚蠢的错误


  • 虽然还不清楚为什么错误只发生在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并不紧急。但我会认真考虑的。