使用GetPrimitiveArrayCritical和Get <PrimitiveType> ArrayRegion之间的权衡是什么?

kee*_*lar 16 c++ java arrays java-native-interface multithreading

当使用JNI桥接c ++和Java时,我们总是希望避免不必要的复制.我发现GetPrimitiveArrayCritical可能会给我们很高的机会不复制数组.但我不完全理解这里记录的限制:

在调用GetPrimitiveArrayCritical之后,本机代码在调用ReleasePrimitiveArrayCritical之前不应该运行很长一段时间.我们必须将这对函数中的代码视为在"关键区域"中运行.在关键区域内,本机代码不能调用其他JNI函数,也不能调用可能导致当前线程阻塞并等待另一个Java线程的任何系统调用.(例如,当前线程不能对另一个Java线程正在写入的流调用read.)

这些限制使得本机代码更有可能获得阵列的未复制版本,即使VM不支持固定.

我的问题是:

  1. 延长一段时间的确切含义是什么?

  2. 那么这是否意味着我们可以安全地调用其他JNI函数或系统调用,它们永远不会导致当前线程阻塞并等待另一个Java线程?

  3. GetPrimitiveArrayCritical是线程安全的吗?

  4. 使用GetPrimitiveArrayCritical而不是GetArrayRegion时,我应该知道什么吗?

小智 18

这里要理解的关键是你在这块内存上获得一个关键部分(例如一个锁).

  1. 延长的时间段旨在表明,一旦你持有这个锁,你就会阻止JVM执行常规操作.所以你应该尽快做你需要做的任何处理.你肯定不想做一些可能会阻止的操作,例如,当你将系统完全停止时.

  2. 你可能能够逃脱它,因为我怀疑这个锁做的主要事情是防止垃圾收集,但是文档很清楚,它不支持调用其他JNI函数的行为.因此,您可能会发现您的代码在JVM的一个版本中运行,而在其他版本中运行.

  3. 因为这是获取锁(关键部分),是的,它是线程安全的.因为它是锁,你不应该持有太长时间(见1).

  4. GetArrayRegion将始终为您提供副本,GetPrimitiveArrayCritical 可能会给您一个副本或者可能会给您一个直接指针.它不确定的原因是它为JVM实现者提供了更多的未来灵活性,以避免直接指针,如果它们会过多地影响一般VM性能(即可能对某些垃圾收集器造成太大影响,使其值得允许锁定).

  • +1为有用的答案.我可以进一步了解你是如何了解这一事实的吗?它在某处记录了吗?另外,由于无法调用其他JNI函数,您是指仅在当前线程中还是在整个程序中? (2认同)

Ale*_*sky 11

GetPrimitiveArrayCritical将阻止所有现有的垃圾收集器.(实验性的Shenandoah收集器通常不会阻塞.)阻塞垃圾收集器将阻止所有对象分配(一旦垃圾堆积起来).

因此,使用规则GetPrimitiveArrayCritical如下:

  1. 不要调用任何JNI函数.各部分的文档并没有充分强调这一点,但这是你必须遵守的规则.原因是JNI函数可能会分配内存,特别是本地引用.因为没有记录哪些JNI函数分配内存,或者多少,所以你不能调用它们中的任何一个.据推测,函数EnsureLocalCapacity可以预先分配本地引用以解决此问题,但没有人记录如何使用它.不要在关键区域内调用除GetPrimitiveArrayCritical,GetStringCritical,ReleasePrimitiveArrayCritical和ReleaseStringCritical之外的JNI函数,否则您将死锁.
  2. 不要以任何其他方式阻止可能需要从堆中分配内存的代码.这主要禁止阻止在同一个VM中运行的Java代码.可以想象(但我不能肯定地说),你可以阻止不分配的Java代码.
  3. 您可以从其他线程调用JNI函数,只要您不阻止等待这些线程.您调用JNI函数的线程可能会停止.见下一点.
  4. 在关键区域花费太多时间将阻止其他线程.根据您运行的线程数及其分配率,您可以在关键区域花费的时间可能会有所不同.在单线程应用程序中,或在多线程应用程序中进行很少的分配,您可以安全地在关键区域中花费无限时间.但是,在其他情况下,您将停止线程,以至于GetPrimitiveArrayCritical完全否定了性能优势.然而,从正确的角度来看,失速是安全的(与死锁相反).
  5. 您可以嵌套Get*CriticalRelease*Critical方法,它们是线程安全的.
  6. 检查返回值nullmode正确设置,因为Get*Critical允许方法失败和/或进行复制,就像Get*ArrayElements.
  7. 如果您正在编写库并正在考虑使用GetPrimitiveArrayCritical,请创建一个运行时选项以Get*ArrayElements代替使用.即使您没有遇到GetPrimitiveArrayCritical引起的拖延,您的用户也可能会遇到.

-Xcheck:jni如果在关键区域内调用JNI函数,Java标志将发出警告.忽略文档说明有时可以在关键区域内调用JNI函数.事实并非如此.

Java 8标志-XX:+PrintJNIGCStalls -XX:+PrintGCDetails将打印有关停滞分配和集合的有用日志消息.要查找的消息可以从src/share/vm/memory/gcLocker.cpp中收集

在Java 9中,日志记录已更改.打开gc和jni的日志记录.要查找的消息可以从src/share/vm/gc/shared/gcLocker.cpp收集

更多信息:

  • @keelar欢迎你!我花了很多时间研究它.我一直在讨论这个问题,但是有一个惊人的,巨大的阻力.图书馆作者不想接受不利的结论,JDK维护者认为文档不需要清理.我写信给Shipilev,后者拒绝做任何事情.我已经写了Java规范勘误表邮件列表,他没有回复. (2认同)
  • @Jenix从阅读(OpenJDK源代码)[http://hg.openjdk.java.net/jdk10/jdk10/hotspot/file/tip/src/share/vm/prims/jni.cpp],我得出结论,GetStringCritical是安全的.任何人做的第一件事就是进入临界区(锁定GC),因此其余代码与处于关键部分兼容.Java 9引入了Compact Strings,大多数字符串都存储为Latin-1.这需要在GetStringCritical中分配和复制到UTF-16.在OpenJDK 9中,此分配位于C堆上,不会使GC死锁.我们可以猜到有努力努力...... (2认同)