openjdk.ruOpenJDK на русском

JEP 171: Fence Intrinsics

Intrinsic-методы барьеров памяти

ОтветственныйDoug Lea
ТипFeature
ОбластьJDK
СтатусClosed / Delivered
Выпуск8
Компонентhotspot / runtime
Обсуждениеhostspot dash dev at openjdk dot java dot net
ОдобренMark Reinhold
Создан2012/11/27 20:00
Обновлён2026/07/29 19:01
Задача8046161

Аннотация

Добавить в класс sun.misc.Unsafe три intrinsic-метода для упорядочения обращений к памяти.

Мотивация

В JVM нет задокументированных механизмов, которые обеспечивали бы упорядочения обращений к памяти, не предусмотренные ни изначально, ни в спецификациях модели памяти JSR 133. (При этом они есть, например, в недавних спецификациях C11/C++11.) К ним относятся несколько конструкций, которые используются в java.util.concurrent и других низкоуровневых библиотеках и сейчас опираются на недокументированные (и, возможно, временные) свойства существующих intrinsic-методов.

Если добавить эти методы на уровне VM, их смогут использовать библиотеки JDK для поддержки возможностей JDK 8. Кроме того, появится возможность позднее сделать базовую функциональность доступной через новые API java.util.concurrent. Это может стать необходимым для разработчиков низкоуровневых библиотек вне JDK, если будущая поддержка модульности закроет другим доступ к этим методам.

Описание

Эти три метода дают три разных вида барьеров памяти. Такие барьеры нужны некоторым компиляторам и процессорам, чтобы определённые обращения (чтения и записи) не переупорядочивались. На практике эти методы действуют точно так же, как существующие методы getXXXVolatile, putXXXVolatile и putOrderedXXX, но сами не обращаются к памяти, а только обеспечивают упорядочение. Однако концептуально они отличаются в одном: согласно текущей JMM, некоторые использования volatile на уровне языка могут переупорядочиваться с некоторыми использованиями не-volatile переменных. Здесь же это не допускается. (В текущих intrinsic-методах это тоже не допускается, но это недокументированное различие между volatile-доступом через intrinsic-методы и volatile-доступом средствами языка.)

Эти три метода:

/**
 * Ensures lack of reordering of loads before the fence
 * with loads or stores after the fence.
 */
void loadFence();

/**
 * Ensures lack of reordering of stores before the fence
 * with loads or stores after the fence.
 */
void storeFence();

/**
 * Ensures lack of reordering of loads or stores before the fence
 * with loads or stores after the fence.
 */
void fullFence();

В этих трёх методах нет ничего «небезопасного». Но по сложившемуся соглашению в Unsafe сейчас находятся родственные методы для volatile- и атомарных операций над отдельными обращениями, поэтому он кажется лучшим местом и для этих методов.

Javadoc можно было бы сделать строже, хотя сформулировать его в терминах JMM нельзя: она не охватывает «volatile-семантику отдельных обращений». Поэтому простая форма описаний, вероятно, лучше всего передаёт замысел целевым пользователям. Кроме того, вероятно, невозможно (скорее всего, из-за существующего кода, который зависит от текущего поведения) одновременно сделать явными и немного ослабить минимально необходимые свойства переупорядочения у существующих intrinsic-методов volatile-доступа. Однако intrinsic-методы барьеров позволяют пользователям однозначно получать эти эффекты, когда это нужно.

Реализация в Hotspot

Эти три метода можно реализовать через существующие методы барьеров acquire, release и volatile, доступные в c1, c2 и интерпретаторе/среде выполнения. Где это применимо, дополнительно подавляются внутренние переупорядочения компилятора и повторное использование значений. Новые базовые возможности для этого не нужны, но всё равно придётся дополнить и адаптировать разбросанный по Hotspot код, без которого не работают новые intrinsic-методы. Если опустить эту часть, набросок реализации такой:

В c2 реализация сводится к методам, из которых убран весь код доступа, как в методах вроде inline_unsafe_access, и остаётся только генерация барьеров памяти. К ним добавляется внутренний барьер CPUOrder, который запрещает переупорядочения при оптимизации. (В случае fullFence вместе с полным барьером volatile из осторожности добавляется и барьер acquire: вдруг существующий код c2 определяет volatile-характер чтений по наличию MemBarAcquire.)

loadFence: {
  insert_mem_bar(Op_MemBarCPUOrder);
  insert_mem_bar(Op_MemBarAcquire);
}

storeFence: {
  insert_mem_bar(Op_MemBarCPUOrder);
  insert_mem_bar(Op_MemBarRelease);
}

fullFence: {
  insert_mem_bar(Op_MemBarCPUOrder);
  insert_mem_bar(Op_MemBarAcquire);
  insert_mem_bar(Op_MemBarVolatile);
}

В c1 можно определить новые узлы с такими действиями LIRGenerator:

loadFence:  { if (os::is_MP()) __ membar_acquire(); }
storeFence: { if (os::is_MP()) __ membar_release(); }
fullFence:  { if (os::is_MP()) __ membar(); }

Кроме того, для всех трёх отключается GVN в ValueMap:

xxxxxFence: { kill_memory(); }

А версии для среды выполнения на C++ (в prims/unsafe.cpp) реализуются через существующие методы OrderAccess:

loadFence:  { OrderAccess::acquire(); }
storeFence: { OrderAccess::release(); }
fullFence:  { OrderAccess::fence(); }

Тестирование

Тестовую инфраструктуру, которую Aleksey Shipilev недавно создал для стресс-тестирования volatile-переменных и атомарных операций, несложно адаптировать. Так можно получить такое же покрытие для обращений, разделённых барьерами, в сравнении с volatile.

Риски и допущения

Мы предполагаем, что инженеры Oracle и дальше будут помогать с интеграцией в JDK 8.