【问题标题】:Is it possible to use sun.misc.Unsafe to call C functions without JNI?是否可以在没有 JNI 的情况下使用 sun.misc.Unsafe 调用 C 函数?
【发布时间】:2016-07-17 19:46:14
【问题描述】:

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

我当然不打算将其用于商业用途。我什至不是专业人士,我只是真的很喜欢编码,而且我最近一直在研究 CUDA,所以我想也许我可以尝试将所有东西混合在一起,但是 JNI 调用的开销会破坏使用 GPU 加速代码的目的。

【问题讨论】:

  • JNI 通过内置到 JVM 中来做到这一点。

标签: java c jvm java-native-interface native-code


【解决方案1】:

JNI 有那么慢吗?

JNI 已经优化了很多,你应该先尝试一下。但它确实有一定的开销,see details

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

关键原住民

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

  • 必须是静态不同步
  • 参数类型必须是primitiveprimitive arrays
  • 实现不得调用 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 的 cmets 中。

基准测试

This benchmark 表明,当本机工作负载非常小时,关键本机可以比常规 JNI 方法快几倍。方法越长,相对开销越小。


P.S. 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

【讨论】:

  • 您如何知道您的程序将使用关键实现还是标准实现?我刚刚尝试了类似的例子,我用javac编译了我的java文件并运行它,它使用了标准的非关键版本。
  • @IvayloToskov 只有 JIT 编译的方法调用关键的 Natives。当一个方法被解释时,它会调用标准实现。
  • 方法声明是否应该同时出现在 .c 和 .h 文件中?
  • @Jaspreet 没有必要将它放在 .h 中
  • jbyteArray 是否总是被分解成一个指针和一个长度值,或者如果需要它仍然是一个对象?我更喜欢接受一个作业然后引用指针来确定它是一个数组还是一个长值。您可以指出 JVM 中的任何来源来更好地解释这种行为吗?
【解决方案2】:

这里值得一提的是,another popular opensource JVM 有一个类似的 documented,但不是流行的方式来加速 JNI 调用一些本地方法。

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

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

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

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

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

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

@FastNative 可以将原生方法的性能提高多达 3 倍,@CriticalNative 可以提高多达 5 倍。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-16
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多