Подтвердить что ты не робот

Какой компромисс между использованием GetPrimitiveArrayCritical и Get <PrimitiveType> ArrayRegion?

При связывании С++ и Java с использованием JNI мы всегда хотим избежать ненужного копирования. Я обнаружил, что GetPrimitiveArrayCritical может дать нам высокую вероятность не копировать массив. Но я не полностью понимаю его ограничение, зарегистрированное здесь:

После вызова GetPrimitiveArrayCritical, внутренний код не должен запускаться в течение длительного периода времени, прежде чем он вызовет ReleasePrimitiveArrayCritical. Мы должны рассматривать код внутри этой пары функций как выполняющийся в "критической области". Внутри критической области собственный код не должен вызывать другие функции JNI или любой системный вызов, который может заставить текущий поток блокировать и ждать другой поток Java. (Например, текущий поток не должен вызывать чтение в потоке, написанном другим потоком Java.)

Эти ограничения делают более вероятным, что собственный код получит неопубликованную версию массива, даже если виртуальная машина не поддерживает пиннинг.

Мои вопросы:

  • Каков точный смысл расширенного периода времени?

  • Значит ли это, что мы можем безопасно вызвать другие функции JNI или системный вызов, которые никогда не заставят текущий поток блокировать и ждать другой поток Java?

  • Является GetPrimitiveArrayCritical потокобезопасным?

  • Есть ли что-нибудь, что я должен знать при использовании GetPrimitiveArrayCritical вместо GetArrayRegion?

4b9b3361

Ответ 1

GetPrimitiveArrayCritical заблокирует все существующие сборщики мусора. (Экспериментальный сборщик Shenandoah часто не блокируется.) Блокировка сборщика мусора блокирует все распределение объектов (как только мусор накапливается).

Следовательно, правила использования GetPrimitiveArrayCritical следующие:

  • Не называть какие-либо функции JNI. Документация в разных частях недостаточно подчеркивает эту точку, но это правило, которому вы должны подчиняться. Причина в том, что функции JNI могут выделять память, особенно локальные ссылки. Поскольку не задокументировано, какие функции JNI распределяют память или сколько, вы не можете назвать ни один из них. Предположительно, функция EnsureLocalCapacity может предварительно назначить локальные ссылки для решения этой проблемы, но никто не документировал, как ее использовать. Не вызывайте функции JNI, кроме GetPrimitiveArrayCritical, GetStringCritical, ReleasePrimitiveArrayCritical и ReleaseStringCritical внутри критической области, или вы будете блокированы.
  • Не блокируйте код, который может потребоваться для выделения памяти из кучи.. Это в основном запрещает блокировку Java-кода, работающего на той же виртуальной машине. Разумеется (но я не могу сказать точно), вы можете блокировать Java-код, который не выделяется.
  • Вам разрешено вызывать функции JNI из других потоков, если вы не блокируете ожидание этих потоков. Потоки, в которых вы вызываете функции JNI, могут останавливаться. См. Следующий пункт.
  • Слишком много времени в критической области остановит другие потоки. В зависимости от того, сколько потоков вы используете и их уровень распределения, количество времени, которое вы можете потратить в критическом регионе, может варьироваться, В однопоточном приложении или в многопоточном, который имеет очень мало распределений, вы можете безопасно проводить неопределенное время в критической области. В других ситуациях, однако, вы будете останавливать потоки настолько, что преимущества GetPrimitiveArrayCritical производительности будут полностью отменены. Однако срыв - это безопасно с точки зрения правильности (в отличие от взаимоблокировки).
  • Вы можете вложить методы Get*Critical и Release*Critical, и они являются потокобезопасными.
  • Проверьте возвращаемое значение для null и установите mode правильно, потому что методы Get*Critical разрешены и/или делают копии, как Get*ArrayElements.
  • Если вы пишете библиотеку и рассматриваете возможность использования GetPrimitiveArrayCritical, создайте параметр времени выполнения, чтобы использовать Get*ArrayElements вместо этого.. Даже если вы не испытываете остановку, возникающую из GetPrimitiveArrayCritical, ваши пользователи может.

Флаг Java -Xcheck:jni предупредит вас, если вы вызовете функции 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

Дополнительная информация:

  • https://shipilev.net/jvm-anatomy-park/9-jni-critical-gclocker/
  • Общедоступной информации об этом мало. В настоящее время я ходатайствую ответственным людям о том, чтобы очистить спецификацию JNI или иным образом устранить путаницу.

Ответ 2

Ключевое значение для понимания здесь - это то, что вы приобретаете критический раздел (например, блокировку) на этой части памяти.

  • расширенный период времени предназначен для указания того, что как только вы удерживаете эту блокировку, вы блокируете JVM от обычных вещей. Поэтому вы должны делать все, что вам нужно сделать, как можно быстрее. Вы, конечно же, не хотите отключать операции do, которые могут блокироваться, например, так как вы довели систему до полной остановки.

  • Возможно, вам удастся с ним справиться, поскольку я подозреваю, что главное, что делает эта блокировка, это предотвращение сбора мусора, но в документации достаточно ясно, что она не поддерживает поведение для вызова других функций JNI. Таким образом, вы можете обнаружить, что ваш код работает в одной версии JVM, а не в других.

  • Поскольку это приобретает блокировку (критический раздел), да, это поточно-безопасный. Поскольку это блокировка, вы не должны удерживать ее слишком долго (см. 1).

  • GetArrayRegion всегда даст вам копию, GetPrimitiveArrayCritical может предоставить вам копию или дать прямой указатель. Причина, по которой это не определенно, дает разработчику JVM более гибкую гибкость, чтобы избежать прямых указателей, если они будут слишком сильно влиять на общую производительность VM (т.е. Может быть слишком много влияния на некоторые сборщики мусора, чтобы сделать это возможным блокирование).

Ответ 3

Метод GetByteArrayElements не может гарантировать, что ваша программа использует ссылку или копию. JNI return isCopy flag для того, чтобы скопировать объект или скопировать его (ссылка означает ссылку). Если вы не хотите копировать его никогда, вам не нужно использовать методы GetArrayElements, потому что он всегда возвращает копию (JVM решает копировать или нет и, вероятно, предпочитает копировать, потому что копия облегчает бремя Мусора Сборщика). Я попробовал это, и я увидел, что мой баран увеличился, когда послал большой массив. Вы также можете увидеть, что по ссылке ниже:

IBM copy and pin (посмотрите на тему копирования и вывода из дерева)

Как говорится в документе, GetPrimitiveArrayCritical возвращает адрес прямой кучи массива Java, отключая сбор мусора до вызова соответствующего ReleasePrimitiveArrayCritical. Поэтому вы должны использовать этот GetPrimitiveArrayCritical, если вы не хотите копировать (вам нужно, когда у вас большой массив).

Для понимания GetArrayRegion вы можете прочитать ниже ссылку:

GetArrayRegion

Я предполагаю, что если вы хотите получить весь массив, используйте GetPrimitiveArrayCritical, если вы хотите получить кусок массива, используйте GetArrayRegion.