Буфер против небезопасных - вне JVM
У меня есть требование использовать пространство в доступной оперативной памяти, которое GC не может контролировать. Я прочитал несколько статей о том же самом, что дало мне представление о двух подходах. Они указаны в следующем коде.
package com.directmemory;
import java.lang.reflect.Field; импорт java.nio.ByteBuffer;
импорт sun.misc.Unsafe;
открытый класс DirectMemoryTest {
public static void main(String[] args) {
//Approach 1
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(8);
directByteBuffer.putDouble(1.0);
directByteBuffer.flip();
System.out.println(directByteBuffer.getDouble());
//Approach 2
Unsafe unsafe = getUnsafe();
long pointer = unsafe.allocateMemory(8);
unsafe.putDouble(pointer, 2.0);
unsafe.putDouble(pointer+8, 3.0);
System.out.println(unsafe.getDouble(pointer));
System.out.println(unsafe.getDouble(pointer+8));
System.out.println(unsafe.getDouble(pointer+16));
}
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
у меня есть пара вопросов
1) Почему я должен когда-либо обращать внимание на подход 1, упомянутый в коде, потому что, согласно моему пониманию, ByteBuffer.allocateDirect() не может вернуть мне буфер с объемом памяти более 2 ГБ? Поэтому, если мое требование состоит в том, чтобы хранить, скажем, 3 ГБ данных, я должен создать новый буфер и хранить там данные, что будет означать, что помимо хранения данных я несу дополнительную ответственность за определение соответствующего буфера (из списка 'n'буферы), который поддерживает указатель на прямую память.
2) Не подходит ли подход 2 немного быстрее, чем подход 1, потому что мне не нужно сначала находить буфер, а затем данные, мне просто нужен механизм индексации для поля объекта и использовать методы getDouble/getInt и передавать абсолютные значения. адрес?
3) Связано ли выделение прямой памяти (правильно ли выделять кучу памяти?) С PID? Если на машине у меня есть 2 процесса Java, вызовы allocateMemory в PID 1 и PID 2 дают мне никогда не пересекающиеся блоки памяти для использования?
4) Почему последний оператор sysout не дает 0.0? Идея состоит в том, что каждый дубль использует 8 байтов, поэтому я сохраняю 1.0 по адресу, возвращенному allocateMemory, скажем address = 1, 2.0 по адресу 1+8, который равен 9, и затем останавливаюсь. Так не должно ли значение по умолчанию быть 0.0?
1 ответ
Следует учитывать, что sun.misc.Unsafe не поддерживается API. Он будет заменен чем-то другим ( http://openjdk.java.net/jeps/260)
1) Если ваш код должен работать без изменений с Java 8 до Java 10 (и более поздних версий), подход 1 с ByteBuffers - это путь.
Если вы готовы заменить использование sun.misc.Unsafe тем, что заменяет Java 9 / Java 10, вы вполне можете использовать sun.misc.Unsafe.
2) Для очень больших структур данных с более чем 2 Гбайт подход 2 может быть быстрее из-за необходимой дополнительной косвенности в подходе 1. Однако без твердого (микро) эталонного теста я бы не стал на это ставить.
3) Выделенная память всегда привязана к текущей запущенной JVM. Таким образом, с двумя JVM, работающими на одной машине, вы не получите пересекающуюся память.
4) Вы выделяете 8 байт неинициализированной памяти. Единственный объем памяти, к которому вы можете легально обращаться сейчас, составляет 8 байт. Для памяти сверх вашего выделенного размера никаких гарантий не дается.
4а) Вы пишете 8 байтов за пределами выделенной памяти (unsafe.putDouble(pointer+8, 3.0);
), что уже приводит к повреждению памяти и может привести к сбою JVM при следующем выделении памяти.
4b) Вы читаете 16 байтов за пределами выделенной памяти, что (в зависимости от архитектуры вашего процессора и операционной системы и предыдущего использования памяти) может привести к немедленному сбою JVM.