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

时间:2014-04-24 01:48:45

标签: java c++ arrays multithreading java-native-interface

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

  

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

     

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

我的问题是:

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

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

  3. GetPrimitiveArrayCritical是否是线程安全的?

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

3 个答案:

答案 0 :(得分:18)

这里要理解的关键是你要在这块记忆中获得一个关键部分(例如锁)。

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

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

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

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

答案 1 :(得分:8)

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. 检查null的返回值并正确设置mode,因为允许Get*Critical方法失败和/或制作副本,就像Get*ArrayElements一样。
  7. 如果您正在编写库并且正在考虑使用GetPrimitiveArrayCritical,请创建一个运行时选项以使用Get*ArrayElements即使您没有遇到由此产生的拖延GetPrimitiveArrayCritical,您的用户可能。
  8. 如果在关键区域内调用JNI函数,Java标志-Xcheck:jni将发出警告。忽略文档说明有时可以在关键区域内调用JNI函数。它不是。

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

    收集

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

    收集

    更多信息:

答案 2 :(得分:1)

GetByteArrayElements方法无法保证您的程序使用引用或复制。 JNI返回isCopy标志,表示它复制了对象或固定它(pin表示引用)。如果你不想永远复制它,你不必使用GetArrayElements方法,因为它总是返回副本(JVM决定是否复制,可能是复制首选,因为复制减轻了垃圾收集器的负担)。我试过了,看到我的ram在发送一个大阵列时增加了。您还可以在以下链接中看到:

IBM copy and pin(从树视图看复制和图钉主题)

正如文档所述,GetPrimitiveArrayCritical返回Java数组的直接堆地址,禁用垃圾收集,直到调用相应的ReleasePrimitiveArrayCritical。所以你必须使用GetPrimitiveArrayCritical,如果你不想复制(当你拥有一个大数组时需要它。)

要了解GetArrayRegion,您可以阅读以下链接:

GetArrayRegion

我想如果你想获得所有数组,请使用GetPrimitiveArrayCritical,如果你想获得一个数组,请使用GetArrayRegion。