Операционные системы выполняют операции ввода-вывода в областях памяти. С точки зрения операционной системы, эти области памяти представляют собой непрерывные последовательности байтов. Поэтому неудивительно, что только байтовые буферы имеют право участвовать в операциях ввода-вывода. Также помните, что операционная система будет напрямую обращаться к адресному пространству процесса, в данном случае процесса JVM, для передачи данных. Это означает, что области памяти, которые являются целями операций ввода-вывода, должны представлять собой непрерывные последовательности байтов. В JVM массив байтов не может храниться в памяти непрерывно, или сборщик мусора может переместить его в любое время. Массивы - это объекты в Java, и способ хранения данных внутри этого объекта может варьироваться от одной реализации JVM к другой.
По этой причине было введено понятие прямого буфера. Прямые буферы предназначены для взаимодействия с каналами и собственными подпрограммами ввода-вывода. Они делают все возможное, чтобы сохранить байтовые элементы в области памяти, которую канал может использовать для прямого или необработанного доступа, используя собственный код, чтобы сообщить операционной системе, что нужно напрямую слить или заполнить область памяти.
Прямые байтовые буферы обычно являются лучшим выбором для операций ввода-вывода. По своей конструкции они поддерживают наиболее эффективный механизм ввода-вывода, доступный JVM. Непрямые байтовые буферы могут быть переданы в каналы, но это может привести к снижению производительности. Обычно непрямой буфер не может быть целью собственной операции ввода-вывода. Если вы передаете непрямой объект ByteBuffer в канал для записи, канал может неявно выполнять следующие действия при каждом вызове:
- Создайте временный прямой объект ByteBuffer.
- Скопируйте содержимое непрямого буфера во временный буфер.
- Выполните операцию ввода-вывода низкого уровня, используя временный буфер.
- Объект временного буфера выходит за пределы области видимости и в конечном итоге собирается сборщиком мусора.
Это потенциально может привести к копированию буфера и оттоку объектов при каждом вводе-выводе, чего мы хотели бы избежать. Однако, в зависимости от реализации, все может быть не так уж плохо. Среда выполнения, скорее всего, будет кэшировать и повторно использовать прямые буферы или выполнять другие хитрые трюки для повышения пропускной способности. Если вы просто создаете буфер для одноразового использования, разница несущественна. С другой стороны, если вы будете многократно использовать буфер в высокопроизводительном сценарии, вам лучше выделить прямые буферы и использовать их повторно.
Прямые буферы оптимальны для ввода-вывода, но их создание может быть дороже, чем непрямые байтовые буферы. Память, используемая прямыми буферами, выделяется путем вызова собственного кода, специфичного для операционной системы, в обход стандартной кучи JVM. Установка и удаление прямых буферов может быть значительно дороже, чем буферы, размещенные в куче, в зависимости от операционной системы хоста и реализации JVM. Области хранения в памяти прямых буферов не подлежат сборке мусора, поскольку они находятся за пределами стандартной кучи JVM.
Компромиссы производительности при использовании прямых и непрямых буферов могут широко варьироваться в зависимости от JVM, операционной системы и дизайна кода. Выделяя память вне кучи, вы можете подвергнуть свое приложение дополнительным воздействиям, о которых JVM не знает. При вводе дополнительных движущихся частей в игру убедитесь, что вы добиваетесь желаемого эффекта. Я рекомендую старый программный принцип: сначала заставьте его работать, а затем сделайте его быстрым. Не беспокойтесь об оптимизации заранее; сконцентрируйтесь в первую очередь на правильности. Реализация JVM может выполнять кэширование буфера или другие оптимизации, которые обеспечат вам необходимую производительность без особых ненужных усилий с вашей стороны.