Подробная модель памяти Java (JMM)

Java

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

Что касается модели памяти Java, то она является необходимым условием для глубокого понимания параллельного программирования на Java. Это очень полезно для безопасности потоков и синхронной асинхронной обработки в последующей многопоточности.

аппаратная архитектура памяти

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

Поэтому для «выжимания» производительности обработки и достижения эффекта «высокого параллелизма» между процессором и запоминающим устройством в качестве буфера добавляется кеш (кеш).

JMM

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

Каждый процессор имеет свой собственный кеш, при совместной работе с одним и тем же блоком, когда несколько процессоров работают одновременно, данные несогласованны, поэтому для гарантии требуется «Cache Consistency Protocol». Например, MSI, Mesi и т.д.

Модель памяти Java

Модель памяти Java — это модель памяти Java, или сокращенно JMM. Он используется для защиты различий в доступе к памяти различного оборудования и операционных систем, чтобы Java-программы могли достигать согласованных эффектов доступа к памяти на всех платформах.

JMM определяет абстрактную связь между потоками и основной памятью: общие переменные между потоками хранятся в основной памяти (основной памяти), каждый поток имеет частную локальную память (локальную память), а локальная память хранит потоки для чтения/записи копий. общих переменных. Локальная память является абстракцией JMM и на самом деле не существует. Он охватывает кеши, буферы записи, регистры и другие оптимизации оборудования и компилятора.

JMM

JMM и структура памяти Java не являются одним и тем же уровнем разделения памяти, и они в основном не связаны. Если вы должны неохотно соответствовать, из определения переменных, основной памяти и рабочей памяти, основная память в основном соответствует части данных экземпляра объекта в куче Java, а рабочая память соответствует части стека виртуальной машины.

JMM

Основная память: объекты экземпляра Java в основном хранятся, а объекты экземпляра, созданные всеми потоками, хранятся в основной памяти, независимо от того, является ли объект экземпляра переменной-членом или локальной переменной (также известной как локальная переменная) в методе. и, конечно же, он также включает информацию об общем классе, константы, статические переменные. Общие области данных, несколько потоков, обращающихся к одной и той же переменной, могут столкнуться с проблемами безопасности потоков.

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

Прямое сравнение между моделью JMM и аппаратной моделью можно упростить, как показано на следующем рисунке:JMM

Взаимодействие между памятью

Копия копии основной памяти переменных, используемых потоком, хранится в рабочей памяти потока.Все операции над переменными потоком должны выполняться в рабочей памяти и не могут напрямую читать и записывать переменные в основная память. Разные потоки не могут напрямую обращаться к переменным в рабочей памяти друг друга, и передачу значений переменных между потоками нужно выполнять через основную память.

JMM

Как показано на рисунке выше, в локальной памяти A и B есть копии общей переменной x в основной памяти, и начальное значение равно 0. После выполнения потока A значение x обновляется до 1 и сохраняется в локальной памяти A. Когда потоку A и потоку B необходимо взаимодействовать, поток A сначала обновляет значение x=1 из локальной памяти в основную память, и значение x в основной памяти становится равным 1. Затем поток B переходит в основную память для чтения обновленного значения x, и значение x локальной памяти потока B также становится равным 1.

Во время этого взаимодействия модель памяти Java определяет 8 операций для завершения, и реализация виртуальной машины должна гарантировать, что каждая операция является атомарной и не может быть разделена снова (за исключением типов double и long).

  • lock: переменная, которая воздействует на основную память и идентифицирует переменную как состояние монопольного потока.
  • разблокировать (разблокировать): переменная, действующая на основную память, она освобождает переменную в заблокированном состоянии, а освобожденная переменная может быть заблокирована другими потоками.
  • read (чтение): переменная, действующая на основную память, которая передает значение переменной из основной памяти в рабочую память потока для последующих действий загрузки.
  • load: переменная, воздействующая на рабочую память, которая помещает значение переменной, полученное операцией чтения из основной памяти, в переменную копию рабочей памяти.
  • использование: переменная, воздействующая на рабочую память, которая передает значение переменной в рабочей памяти механизму выполнения, который будет выполняться всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которая должна использовать значение переменной.
  • assign (назначение): переменная, которая воздействует на рабочую память, она присваивает значение, полученное от исполняющего движка, переменной в рабочей памяти, и выполняет эту операцию всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которая присваивает значение переменной.
  • store (хранение): переменная, действующая на рабочую память, которая передает значение переменной из рабочей памяти в основную память для последующих операций записи.
  • write (запись): переменная, действующая на основную память, которая помещает значение переменной, полученное из рабочей памяти операцией сохранения, в переменную основной памяти.

Если вам нужно скопировать переменную из основной памяти в рабочую память, она должна будет выполнять операции чтения и загрузки последовательно, если мы хотим, чтобы переменная синхронизировалась обратно в основную память из рабочей памяти, необходимо выполнить операции сохранения и записи последовательно. Обратите внимание, что модель памяти Java требует, чтобы только две описанные выше операции выполнялись последовательно, но не гарантируется, что они будут выполняться непрерывно. То есть между чтением и загрузкой, между сохранением и записью можно вставить другие инструкции, такие как переменные основной памяти a, b при доступе, порядок чтения, скорее всего, будет запускать a, b читать, загружать b, загружать a. Кроме того, модель памяти Java также предусматривает соблюдение следующих правил при выполнении вышеописанных 8 основных операций.

  • Одна из операций чтения и загрузки, сохранения и записи не может появляться одна, то есть переменная не может быть прочитана из основной памяти, но не принята в рабочую память, или обратная запись инициирована из рабочей памяти, но не принимается основной памятью.
  • Поток не может отменить свою последнюю операцию присваивания, то есть после того, как переменная была изменена в рабочей памяти, он должен синхронизировать изменение обратно в основную память.
  • Потоку не разрешено синхронизировать данные из рабочей памяти потока обратно в основную память без какой-либо причины (операция присваивания не выполнялась).
  • Новая переменная может «родиться» только в основной памяти, а использовать неинициализированные (загрузить или присвоить) переменную непосредственно в рабочей памяти нельзя, т. е. перед реализацией операций использования и сохранения над переменной, это должно быть Операции назначения и загрузки были выполнены первыми.
  • Переменная может быть заблокирована только одним потоком одновременно, но операция блокировки может повторяться одним и тем же потоком несколько раз.После многократного выполнения блокировки переменная будет разблокирована только после такого же количества операций разблокировки. выполненный.
  • Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, необходимо повторно выполнить операцию загрузки или назначения, чтобы инициализировать значение переменной.
  • Если переменная ранее не была заблокирована операцией блокировки, не разрешается выполнять над ней операцию разблокировки или разблокировать переменную, заблокированную другим потоком.
  • Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована обратно в основную память (выполняются операции сохранения и записи).

Специальные правила для переменных типа long и double

Модель памяти Java требует, чтобы восемь операций блокировки, разблокировки, чтения, загрузки, назначения, использования, хранения и записи были атомарными, но для 64-битных типов данных (длинных или двойных) в модели определено относительно свободное определение. Оговорено, что виртуальной машине разрешено разделять операции чтения и записи 64-битных данных, которые не модифицируются volatile на две 32-битные операции, то есть виртуальной машине разрешено выбирать загрузку, сохранение, чтение , и пишут, что не гарантируют 64-битные типы данных.Атомарность этих 4-х операций, неатомарный контракт long и double.

Если тип double или long не объявлен как volatile в случае многопоточности, может быть значение "полупеременной", то есть ни исходное значение, ни измененное значение.

Хотя спецификация Java допускает описанную выше реализацию, атомарные операции в основном используются в коммерческих виртуальных машинах, поэтому практически невозможно прочитать «половину переменной» в повседневном использовании.

резюме

Этот урок посвящен модели памяти Java, а также шагам и операциям взаимодействия с памятью. В следующей статье мы сосредоточимся на нескольких особенностях и принципах модели памяти Java. Добро пожаловать в общедоступную учетную запись WeChat «Program New Horizon», чтобы получать последние обновления статей как можно скорее.

Оригинальная ссылка: "Подробная модель памяти Java (JMM)

Серия статей "Интервьюер":


Программа Новые Горизонты: Захватывающие и растущие нельзя пропустить

程序新视界-微信公众号