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

Как JVM обеспечивает безопасность потоков для выделения памяти для нового объекта

Предположим, что это должно произойти в настоящей параллельной среде, одной VM, в то же время:

// Thread 1:
  new Cat()

// Thread 2:
  new Dog()

// Thread 3:
  new Mouse()

Как JVM обеспечивает безопасность потоков при распределении памяти в куче?

Куча одна для всех потоков и имеет свои собственные внутренние данные.

Для простоты предположим, что простая сборка сборщика мусора, -XX: + UseSerialGC -XX: + UseParallelGC, с простым инкрементным указателем, чтобы отметить начало свободного пространства и одно непрерывное свободное пространство в Eden (куча).

Должна быть какая-то синхронизация между потоками, когда кучное пространство выделяется для экземпляров Cat, Dog и Mouse, иначе они могут легко переписать друг друга. Означает ли это, что каждый новый оператор скрывается внутри некоторых синхронизированных блоков? Таким образом, многие "свободные от блокировки" алгоритмы фактически не блокируются полностью;)

Я предполагаю, что выделение памяти производится самим потоком приложения, синхронно, а не другим выделенным потоком (-ами).

Я знаю TLAB s или Буфер локального распределения потока. Они позволяют потокам иметь отдельные области памяти в Eden для распределения, поэтому синхронизация не требуется. Но я не уверен, что TLAB установлен по умолчанию, это немного непонятная функция HotSpot. Примечание: не путайте переменные TLAB и ThreadLocal!

Я также предполагаю, что с более сложными сборщиками мусора, такими как G1 или некомпактными сборщиками мусора, необходимо сохранить более сложные данные структуры кучи, например список свободных блоков для CMS, поэтому требуется больше синхронизации.

ОБНОВЛЕНИЕ. Прошу пояснить это. Я принимаю ответ для реализации и вариантов JVM для HotSpot с активным TLAB и без него.

UPDATE: Согласно мой быстрый тест, TLAB установлены по умолчанию, на моем 64-битном JDK 7 для Serial, Параллельные и сборщики мусора CMS, но не для G1 GC.

4b9b3361

Ответ 1

Я кратко описал процедуру распределения в JVM HotSpot в этом ответе.
Способ распределения объекта зависит от области кучи, где она выделена.

1. TLAB. Самый быстрый и самый частый путь.

TLAB - это области в Eden, зарезервированные для локальных распределений потоков. Каждый поток может создавать много TLAB: как только один заполняется, новый TLAB создается с использованием метода, описанного в № 2. То есть создание нового TLAB - это нечто вроде выделения большого метаобъекта непосредственно в Eden.

Каждый поток Java имеет два указателя: tlab_top и tlab_limit. Выделение в TLAB - это просто шаг указателя. Синхронизация не требуется, поскольку указатели являются локальными по потоку.

if (tlab_top + object_size <= tlab_limit) {
    new_object_address = tlab_top;
    tlab_top += object_size;
}

-XX:+UseTLAB включен по умолчанию. Если вы отключите его, объекты будут выделены в Eden, как описано ниже.

2. Распределение в Eden (Young Generation).

Если в TLAB недостаточно места для нового объекта, создается либо новый TLAB, либо объект выделяется непосредственно в Eden (в зависимости от лимита отходов TLAB и других параметров эргономики).

Распределение в Eden аналогично распределению в TLAB. Существуют также два указателя: eden_top и eden_end, они являются глобальными для всей JVM. Распределение также является приращением указателя, но используются атомарные операции, поскольку пространство Eden разделяется между всеми потоками. Безопасность потоков достигается с помощью атомных команд, специфичных для архитектуры: CAS (например, LOCK CMPXCHG на x86) или LL/SC (на ARM).

3. Распределение в старом поколении.

Это зависит от алгоритма GC, например. CMS использует бесплатные списки. Распределение в старом поколении обычно выполняется только самим сборщиком мусора, поэтому он знает, как синхронизировать свои собственные потоки (как правило, с сочетанием буферов распределения, блокирующих атомных операций и мьютексов).

Ответ 2

Это не указано в спецификации Java. Это означает, что каждый JVM может это сделать, однако он хочет, пока он работает, и следует за гарантиями памяти Java.

Хорошая догадка о том, как это работает с движущимся GC, будет каждый поток получает свою собственную зону выделения. При увеличении простого указателя при распределении объектов. Очень простое и очень быстрое распределение без блокировки. Когда он заполнен, он получает новую выделенную зону выделения, или GC перемещает все живые объекты в непрерывную часть кучи и возвращает теперь пустые зоны обратно в каждый поток. Я не уверен, что так оно реализовано в любой JVM, и это будет сложно с синхронизацией GC.