Распределение собственной памяти прямого буфера в Java для JOGL

Я использую прямые буферы (java.nio) для хранения информации о вершинах для JOGL. Эти буферы велики и заменяются несколько раз в течение срока службы приложения. Память не освобождается во времени, и у меня заканчивается память после нескольких замен.

Кажется, что нет хорошего способа освободить, используя буферные классы java.nio. У меня вопрос такой:

Есть ли какой-нибудь метод в JOGL для удаления Direct Buffers? Я смотрю в glDeleteBuffer(), но кажется, что это только удаляет буфер из памяти видеокарты.

Спасибо

5 ответов

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

Когда вы знаете, что прямой буфер NIO стал для вас бесполезным, вы должны освободить его собственную память, используя его sun.misc.Cleaner (StaxMan прав) и вызывать clean() (кроме Apache Harmony), вызывать free() (с Apache Harmony) или используйте лучший публичный API для этого (возможно, в Java >= 1.9, AutoCleaning, расширяющий AutoCloseable?).

Это не работа JOGL, вы можете использовать простой Java-код, чтобы сделать это самостоятельно. Мой пример под GPL v2, и этот пример под более разрешительной лицензией.

Редактировать.: Мой последний пример работает даже с Java 1.9 и поддерживает OpenJDK, Oracle Java, Sun Java, Apache Harmony, GNU Classpath и Android. Возможно, вам придется удалить некоторый синтаксический сахар, чтобы он работал с Java < 1,7 (мульти-уловы, алмазы и дженерики).

Ссылка: http://www.ibm.com/developerworks/library/j-nativememory-linux/

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

Ссылка: http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html

Содержимое прямых буферов может находиться вне обычной кучи мусора

Это решение (в этом JEP, все еще черновик, вероятно, недоступно в Java 1.9) очень многообещающе, нам не нужно будет использовать непубличные API.

public long memory(long index) {
    // The scope where the memory region is available
    // Implements AutoClosable but `close` can be called manually as well
    try (Scope scope = new NativeScope()) {
        // Allocate the actual memory area, in this case in the style of a "long-array"
        Pointer<Long> ptr = scope.allocate(
                    NativeLibrary.createLayout(long.class), numElements);

        // Get the reference to a certain element
        Reference<Long> ref = ptr.offset(index).deref();

        // Set a value to this element through the reference
        ref.set(Long.MAX_VALUE);

        // Read the value of an element
        return ref.get();
    }
}

Примечание: sun.misc.Cleaner был перемещен в jdk.internal.ref.Cleaner в Java 1.9 в модуле "java.base", но последний реализует java.lang.Runnable (спасибо Алану Бейтману за напоминание об этой разнице). Затем вам просто нужно навести уборщик на Runnable и вызвать метод run() (не называйте это отражением, чтобы избежать получения исключения java.lang.IllegalAccessException). Это работает, я только что протестировал (6 августа 2016 года) с Java 1.9 Early Access build 129.

Однако jdk.internal.ref.Cleaner может быть позже перемещен в java.ref.Cleaner$Cleanable.

В Lucene есть хороший пример под более разрешительной лицензией.

Прямые буферы сложны и не имеют обычных гарантий сборки мусора - см. Более подробно: http://download.oracle.com/javase/1.4.2/docs/api/java/nio/ByteBuffer.html

Если у вас возникли проблемы, я бы посоветовал выделить один раз и повторно использовать буфер, а не распределять и освобождать повторно.

Способ, которым выполняется освобождение, ужасен - мягкая ссылка в основном вставляется в объект Cleaner, который затем выполняет освобождение, когда владелец ByteBuffer собирает мусор. Но это на самом деле не гарантируется, что будет вызвано своевременно.

Удаление прямого буфера - это работа, выполняемая сборщиком мусора через некоторое время после пометки объекта Byte Buffer.

Вы можете попытаться вызвать gc сразу после удаления последней ссылки на ваш буфер. По крайней мере, есть шанс, что память будет свободна, немного быстрее.

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

Протестировано и работает в Java SE версии 10.0.2.

public final class BufferUtil {

    //various buffer creation utility functions etc. etc.

    protected static final sun.misc.Unsafe unsafe = AccessController.doPrivileged(new PrivilegedAction<sun.misc.Unsafe>() {
        @Override
        public Unsafe run() {
            try {
                Field f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                return (Unsafe) f.get(null);
            } catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                throw new RuntimeException(ex);
            }
        }
    });

    /** Frees the specified buffer's direct memory allocation.<br>
     * The buffer should not be used after calling this method; you should
     * instead allow it to be garbage-collected by removing all references of it
     * from your program.
     * 
     * @param directBuffer The direct buffer whose memory allocation will be
     *            freed
     * @return Whether or not the memory allocation was freed */
    public static final boolean freeDirectBufferMemory(ByteBuffer directBuffer) {
        if(!directBuffer.isDirect()) {
            return false;
        }
        try {
            unsafe.invokeCleaner(directBuffer);
            return true;
        } catch(IllegalArgumentException ex) {
            ex.printStackTrace();
            return false;
        }
    }

}

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

Напишите некоторый JNI, который переносит malloc с помощью NewDirectByteBuffer (не забудьте установить порядок), и аналогичную функцию для его освобождения.

Другие вопросы по тегам