2014-04-24 66 views
13

使用JNI橋接C++和Java時,我們總是希望避免不必要的複製。我發現GetPrimitiveArrayCritical可能會給我們一個不復制數組的機會。但我不完全理解它的限制記載here使用GetPrimitiveArrayCritical和Get <PrimitiveType> ArrayRegion之間的權衡是什麼?

調用GetPrimitiveArrayCritical後,本機代碼不應該的時間長一段時間它會調用ReleasePrimitiveArrayCritical之前運行。我們必須將這對函數中的代碼視爲在「關鍵區域」中運行。在關鍵區域內部,本機代碼不能調用其他JNI函數或任何可能導致當前線程阻塞並等待另一個Java線程的系統調用。 (例如,當前線程不能在另一個Java線程正在寫入的流上調用讀取)。

這些限制使得本機代碼更有可能獲得數組的未複製版本,即使虛擬機不支持鎖定。

我的問題是:

  1. 什麼是時間長時間的確切含義是什麼?

  2. 這是否意味着我們可以安全地調用其它的JNI函數或系統調用,將不會引起當前線程阻塞和等待另一個Java線程?

  3. 是GetPrimitiveArrayCritical線程安全的?

  4. 當使用GetPrimitiveArrayCritical而不是GetArrayRegion時,有什麼我應該知道的嗎?

回答

3

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引起的拖延,您的用戶也可能會遇到。

如果您在關鍵區域內調用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

更多信息收集:

+0

這是我見過的最詳細的答案。感謝您的詳細解答! – keelar

+1

@keelar不客氣!我花了很多時間研究它。我一直在討論這個問題,但有一個驚人的,巨大的阻力。庫作者不想接受不利的結論,JDK維護人員不認爲需要清理文檔。我寫信給了Shipilev,他拒絕做任何事情。我寫了Java規範勘誤郵件列表,誰沒有回覆。 –

16

這裏要理解的關鍵是您正在獲取關於這段內存的關鍵部分(例如鎖)。

  1. 的時間長期是爲了表明,一旦你保持此鎖,你從這樣做是平常的事情擋住了JVM。所以你應該儘可能快地做你需要做的任何處理。你肯定不希望走下車可能會阻止一個做操作,例如,當你想使系統完全停止。

  2. 你也許能夠擺脫它,因爲我懷疑這個鎖的主要作用是防止垃圾回收,但文檔非常清楚,它不支持調用其他JNI函數的行爲。所以你可能會發現你的代碼在JVM的一個版本中工作,而不是其他的。

  3. 由於這是獲取鎖(臨界區),是的,它是線程安全的。因爲它是一個鎖,所以你不應該把它擱得太久(見1)。

  4. GetArrayRegion總會給你一個副本,GetPrimitiveArrayCritical 可能給你的副本或可能給你一個直接的指針。它不能確定的原因是它爲JVM實現者提供了更多的未來的靈活性,以避免直接指針的影響,因爲它們會影響一般的VM性能。可能會對一些垃圾收集器產生太大的影響,使其值得鎖定)。

+1

+1爲有用的答案。我能否進一步瞭解您如何瞭解這一事實?它在什麼地方記錄?另外,由於不能調用其他的JNI函數,你是指在當前線程還是在整個程序中? – keelar

1

GetByteArrayElements方法不能保證您的程序使用引用或複製。 JNI返回isCopy標誌,表示它複製對象或固定它(引腳表示引用)。如果你不想複製它從來沒有,你沒有使用GetArrayElements方法,因爲它總是返回副本(JVM決定複製與否,並可能複製首選,因爲複製緩解垃圾收集器的負擔)。我試了一下,發現陣容大的時候我的內存增加了。你也可以看到,在下面的鏈接:

IBM copy and pin(看從樹狀複製和銷主體)

由於文件說,GetPrimitiveArrayCritical返回Java數組的直接堆地址,禁止垃圾收集,直到相應的ReleasePrimitiveArrayCritical叫做。所以你必須使用GetPrimitiveArrayCritical,如果你不想複製(當你有一個大數組時,你需要這個)。

對於理解GetArrayRegion,你可以閱讀下面的鏈接:

GetArrayRegion

我想,如果你想獲得的所有陣列,使用GetPrimitiveArrayCritical,如果你想獲得一塊陣列,使用GetArrayRegion 。

相關問題