Примеры принудительного освобождения родной памяти, выделенной прямым ByteBuffer, используя sun.misc.Unsafe?
JDK предоставляет возможность выделять так называемые прямые ByteBuffers, где память выделяется вне кучи Java. Это может быть полезно, поскольку сборщик мусора не затрагивает эту память и, как таковой, не влияет на издержки GC: это очень полезно для свойства для долгоживущих вещей, таких как кэши.
Тем не менее, существует одна критическая проблема с существующей реализацией: базовая память выделяется асинхронно только тогда, когда владеющий ByteBuffer собирается мусором; нет способа форсировать раннее освобождение. Это может быть проблематично, так как на сам цикл GC не влияет обработка ByteBuffers, и, учитывая, что ByteBuffers, вероятно, находятся в области памяти старого поколения, возможно, что GC вызывается через несколько часов после того, как ByteBuffer больше не используется.
Но в теории должно быть возможно использовать sun.misc.Unsafe
методы (freeMemory, allocateMemory) напрямую: это то, что сам JDK использует для выделения / освобождения собственной памяти. Глядя на код, одна потенциальная проблема, которую я вижу, это возможность двойного освобождения памяти, поэтому я хотел бы убедиться, что состояние будет очищено должным образом.
Может кто-нибудь указать мне код, который делает это? В идеале хотелось бы использовать это вместо JNA.
ПРИМЕЧАНИЕ: я видел этот вопрос, который как-то связан.
Похоже, что указанные ответы - хороший путь: вот пример кода из Elastic Search, который использует эту идею. Спасибо всем!
3 ответа
С помощью sun.misc.Unsafe
вряд ли возможно, потому что базовый адрес выделенной собственной памяти является локальной переменной java.nio.DirectByteBuffer
конструктор.
На самом деле вы можете принудительно освободить собственную память с помощью следующего кода:
import sun.misc.Cleaner;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
...
public static void main(String[] args) throws Exception {
ByteBuffer direct = ByteBuffer.allocateDirect(1024);
Field cleanerField = direct.getClass().getDeclaredField("cleaner");
cleanerField.setAccessible(true);
Cleaner cleaner = (Cleaner) cleanerField.get(direct);
cleaner.clean();
}
Существует гораздо более простой способ очистки памяти.
public static void clean(ByteBuffer bb) {
if(bb == null) return;
Cleaner cleaner = ((DirectBuffer) bb).cleaner();
if (cleaner != null) cleaner.clean();
}
Использование этого может иметь большое значение, если вы отбрасываете прямой или отображенный в памяти ByteBuffer довольно быстро.
Одна из причин использования Cleaner для этого заключается в том, что вы можете иметь несколько копий базовых ресурсов памяти, например, с помощью slice(). и уборщик имеет подсчет ресурсов из них.
В основном вы хотите следовать той же семантике, что и при использовании потоков ввода-вывода. Точно так же, как вам нужно закрыть поток один раз, вам нужно освободить память один раз. Таким образом, вы можете написать свою собственную оболочку вокруг нативных вызовов, делая возможным раннее освобождение памяти