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

25
一段 C/C++ 代码可以为 JNI 方法提供一个函数指针数组。但是有没有一种方法可以直接从 Java 代码内部调用该数组指向的函数指针所指向的堆栈,而不使用 JNI 或类似技术?JNI 可以通过某种方式实现这样的操作,因此必须有一种方法。JNI 是如何做到的?是通过 sun.misc.Unsafe 吗?即使不是,我们是否可以使用某些 Unsafe 的解决方案来获取执行此操作的 JVM 代码?
当然我不打算商业化使用。我甚至不是专业人士,我只是非常喜欢编码,最近一直在学习 CUDA,所以想尝试混合所有东西,但是 JNI 调用的开销会破坏 GPU 加速代码的目的。

JNI是通过内置于JVM中来实现的。 - user253751
2个回答

77

JNI是否很慢?

JNI已经进行了很多优化,你应该先尝试使用它。但是确实存在一定的开销,详见细节

如果一个本地函数简单并且频繁调用,这种开销可能会很大。JDK有一个私有API叫做Critical Natives,可以减少调用不需要太多JNI功能的函数的开销。

Critical Natives

一个本地方法必须满足以下条件才能成为Critical Native:

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

Critical Native的声明看起来像一个常规的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);
}

Critical natives仅在JDK 7及以上的HotSpot JVM中得到支持。此外,“critical”版本仅从编译代码中调用。因此,为了使其正常工作,您需要同时使用关键和标准实现。
该功能是为JDK内部使用而设计的。没有公共规范或其他东西。可能唯一可以找到的文档是在JDK-7013347的注释中。
基准测试 这个基准测试显示,当本地负载非常小的时候,关键本地方法可以比常规JNI方法快几倍。方法越长,相对开销就越小。

Performance of JNI calls


P.S. JDK正在进行一个工作,实现本地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

你如何知道你的程序将使用关键实现还是标准实现?我刚刚尝试了类似的例子,我用javac编译了我的Java文件并运行它,它使用了标准的非关键版本。 - Ivaylo Toskov
1
@IvayloToskov 只有 JIT 编译的方法才会调用关键本地方法。当一个方法被解释时,它将调用标准实现。 - apangin
方法声明应该同时存在于 .c 和 .h 文件中吗? - Jaspreet
1
@Jaspreet 不需要把它放在.h文件中。 - apangin
jbyteArray是否总是会被分解成指针和长度值,或者如果需要的话,它是否仍然是一个jobject?我更喜欢获取一个jobject,然后引用指针来确定它是一个数组还是一个长整型值。您能指出JVM中任何可以解释这种行为的来源吗? - Johnny V
显示剩余6条评论

3
值得一提的是,另一个流行的开源JVM也有一种类似的已记录的但未被推广的方法,用于加速某些本地方法的JNI调用。
可以使用@FastNative@CriticalNative注释来加速Java Native Interface (JNI)的本地调用。这些内置ART运行时优化可以加速JNI转换并替代现在已弃用的!bang JNI符号。这些注释对非本地方法没有影响,并且仅适用于引导类路径上的平台Java语言代码(无Play Store更新)。 @FastNative注释支持非静态方法。如果方法将jobject作为参数或返回值访问,则使用此选项。 @CriticalNative注释提供了一种更快速的运行本地方法的方式,具有以下限制:
- 方法必须是静态的 - 没有参数、返回值或隐式this的对象。 - 只传递原始类型到本地方法。 - 本地方法在其函数定义中不使用JNIEnv和jclass参数。 - 必须使用RegisterNatives注册该方法,而不是依赖动态JNI链接。 @FastNative@CriticalNative注释在执行本地方法时禁用垃圾收集。不要在长时间运行的方法中使用它们,包括通常快速但通常不受限制的方法。
垃圾回收的暂停可能会导致死锁。如果锁定未在本地释放(即在返回到托管代码之前),则不要在快速本地调用期间获取锁定。这不适用于常规JNI调用,因为ART将执行本地代码视为已暂停。 @FastNative可以将本地方法性能提高3倍,@CriticalNative可以将其提高5倍。
此文档涉及现已弃用的!bang标记,该标记用于加快Dalvik JVM上的某些本地调用速度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接