是否可以使用 sun.misc.Unsafe 在没有 JNI 的情况下调用 C 函数?

一段 C/C++ 代码可以提供一个包含函数指针数组的 JNI 方法。但是有没有办法直接从Java代码内部调用数组指针所指向的函数的堆栈(不使用JNI或类似代码)?JNI以某种方式做了类似的事情,所以一定有办法。JNI是如何做到的?它是通过sun.misc.Unsafe吗?即使不是,我们是否可以使用一些不安全的解决方法来获取执行此操作的JVM代码?

当然,我不打算将其用于商业用途。我甚至不是专业人士,我只是非常喜欢编码,我最近一直在学习CUDA,所以我想也许我可以尝试将所有内容混合在一起,但是JNI调用的开销会破坏GPU加速代码的目的。


答案 1

JNI有那么慢吗?

JNI已经进行了大量优化,您应该先尝试一下。但它确实有一定的开销,请参阅详细信息

如果本机函数很简单并且经常被调用,则此开销可能很大。JDK 有一个名为 Critical Natives 的私有 API,以减少调用不需要太多 JNI 功能的函数的开销。

关键原住民

本机方法必须满足以下条件才能成为关键本机方法:

  • 必须是静态的,不同步;
  • 参数类型必须是基元数组或基元数组;
  • 实现不得调用 JNI 函数,即它不能分配 Java 对象或引发异常;
  • 不应该长时间运行,因为它会在运行时阻塞GC

关键本机的声明看起来像常规的 JNI 方法,除了

  • 它以而不是JavaCritical_Java_;
  • 它没有额外的和参数;JNIEnv*jclass
  • Java 数组在两个参数中传递:第一个是数组长度,第二个是指向原始数组数据的指针。也就是说,无需打电话和朋友,就可以立即使用直接数组指针。GetArrayElements

例如,JNI 方法

JNIEXPORT jint JNICALL
Java_com_package_MyClass_nativeMethod(JNIEnv* env, jclass klass, jbyteArray array) {
    jboolean isCopy;
    jint length = (*env)->GetArrayLength(env, array);
    jbyte* buf = (*env)->GetByteArrayElements(env, array, &isCopy);
    jint result = process(buf, length);
    (*env)->ReleaseByteArrayElements(env, array, buf, JNI_ABORT);
    return result;    
}

将转到

JNIEXPORT jint JNICALL
JavaCritical_com_package_MyClass_nativeMethod(jint length, jbyte* buf) {
    return process(buf, length);
}

关键原生仅在从 JDK 7 开始的 HotSpot JVM 中受支持。此外,“关键”版本仅从编译的代码中调用。因此,您需要关键和标准实现才能使其正常工作。

此功能是为在 JDK 中内部使用而设计的。没有公共规范或其他东西。您可能找到的唯一文档是在JDK-7013347的注释中。

基准

此基准测试表明,当本机工作负载非常小时,关键本机可能比常规 JNI 方法快几倍。方法越长,相对开销越小。

Performance of JNI calls


附言JDK 中正在进行一项实现 Native MethodHandles 的工作,这将作为 JNI 的更快替代方案。但是,它不太可能出现在JDK 10之前。

  1. http://cr.openjdk.java.net/~jrose/panama/native-call-primitive.html
  2. http://mail.openjdk.java.net/pipermail/panama-dev/2015-December/000225.html

答案 2

这里值得一提的是,另一个流行的开源JVM有一种类似的,有文档记录的,但不是普及的方式来加速JNI对一些原生方法的调用。

使用@FastNative@CriticalNative注释,可以更快地调用 Java 本机接口 (JNI)。这些内置的 ART 运行时优化加快了 JNI 转换,并取代了现已弃用的 !bang JNI 表示法。注释对非本机方法没有影响,并且仅适用于引导类路径上的平台 Java 语言代码(无 Play 商店更新)。

@FastNative注释支持非静态方法。如果方法将 jobject 作为参数或返回值访问,请使用此选项。

@CriticalNative注释提供了一种运行本机方法的更快方法,但存在以下限制:

  • 方法必须是静态的 - 没有参数对象、返回值或隐式 this。
  • 只有基元类型传递给本机方法。
  • 本机方法在其函数定义中不使用 JNIEnv 和 jclass 参数。
  • 该方法必须注册到 RegisterNatives,而不是依赖于动态 JNI 链接。

@FastNative@CriticalNative批注在执行本机方法时禁用垃圾回收。不要与长时间运行的方法一起使用,包括通常快速但通常不受限制的方法。

暂停垃圾回收可能会导致死锁。如果锁尚未在本地释放(即在返回到托管代码之前),则在快速本机调用期间不要获取锁。这不适用于常规的 JNI 调用,因为 ART 将正在执行的本机代码视为已挂起。

@FastNative可以将本机方法性能提高多达 3 倍,@CriticalNative提高 5 倍。

本文档引用了现已弃用的 !bang 表示法,该表示法用于加速 Dalvik JVM 上的一些本机调用。


推荐