Как собрать мусор прямым буфером java

У меня есть утечка памяти, которую я изолировал от неправильно расположенных прямых байтовых буферов.

ByteBuffer buff = ByteBuffer.allocateDirect (7777777)

GC собирает объекты, которые содержат эти буферы, но не избавляется от самого буфера. Если я создаю достаточно много временных объектов, содержащих буферы, я получаю это обнадеживающее сообщение.

java.lang.OutOfMemoryError: прямая буферная память

Я искал эту проблему и, видимо,

buff.clear

а также

 System.gc()

не работает

7 ответов

Решение

Я подозреваю, что где-то в вашем приложении есть ссылка на экземпляр (ы) ByteBuffer, и это препятствует сборке мусора.

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

DBB будет освобожден, как только он попадет в очередь ссылок, и будет запущен финализатор. Однако, поскольку мы не можем зависеть от запуска финализатора, мы можем использовать отражение, чтобы вручную вызвать его "очиститель".

Используя отражение:

/**
* DirectByteBuffers are garbage collected by using a phantom reference and a
* reference queue. Every once a while, the JVM checks the reference queue and
* cleans the DirectByteBuffers. However, as this doesn't happen
* immediately after discarding all references to a DirectByteBuffer, it's
* easy to OutOfMemoryError yourself using DirectByteBuffers. This function
* explicitly calls the Cleaner method of a DirectByteBuffer.
* 
* @param toBeDestroyed
*          The DirectByteBuffer that will be "cleaned". Utilizes reflection.
*          
*/
public static void destroyDirectByteBuffer(ByteBuffer toBeDestroyed)
    throws IllegalArgumentException, IllegalAccessException,
    InvocationTargetException, SecurityException, NoSuchMethodException {

  Preconditions.checkArgument(toBeDestroyed.isDirect(),
      "toBeDestroyed isn't direct!");

  Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
  cleanerMethod.setAccessible(true);
  Object cleaner = cleanerMethod.invoke(toBeDestroyed);
  Method cleanMethod = cleaner.getClass().getMethod("clean");
  cleanMethod.setAccessible(true);
  cleanMethod.invoke(cleaner);

}

ByteBuffer Документация гласит:

Прямой байтовый буфер может быть создан путем вызова allocateDirect фабричный метод этого класса. Буферы, возвращаемые этим методом, обычно имеют несколько более высокие затраты на выделение и освобождение, чем непрямые буферы. Содержимое прямых буферов может находиться за пределами обычной кучи, собираемой мусором, и поэтому его влияние на объем памяти приложения может быть неочевидным. Поэтому рекомендуется, чтобы прямые буферы были выделены главным образом для больших, долговечных буферов, которые подчиняются собственным операциям ввода-вывода базовой системы. В общем случае лучше всего размещать прямые буферы только тогда, когда они дают ощутимый выигрыш в производительности программы.

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

Выделенная память реализуется через собственную библиотеку. Эта память будет освобождена при вызове метода ByteBuffer#finalize, то есть, когда Buffer gc'd. Посмотрите на реализации allocate () и finalize() DirectByteBufferImpl.

buff.clear() не обязательно, System.gc() поможет только в том случае, если, как и другие, уже упоминалось, больше нет ссылки на объект ByteBuffer.

Вот улучшенная реализация, которая будет работать для любого прямого буфера:

public static void destroyBuffer(Buffer buffer) {
    if(buffer.isDirect()) {
        try {
            if(!buffer.getClass().getName().equals("java.nio.DirectByteBuffer")) {
                Field attField = buffer.getClass().getDeclaredField("att");
                attField.setAccessible(true);
                buffer = (Buffer) attField.get(buffer);
            }

            Method cleanerMethod = buffer.getClass().getMethod("cleaner");
            cleanerMethod.setAccessible(true);
            Object cleaner = cleanerMethod.invoke(buffer);
            Method cleanMethod = cleaner.getClass().getMethod("clean");
            cleanMethod.setAccessible(true);
            cleanMethod.invoke(cleaner);
        } catch(Exception e) {
            throw new QuartetRuntimeException("Could not destroy direct buffer " + buffer, e);
        }
    }
}

Пока вы полагаетесь на конкретную реализацию sun (oracle), лучшим выбором, чем попытка изменить видимость java.nio.DirectByteBuffer, является использование интерфейса sun.nio.ch.DirectBuffer с помощью отражений.

/**
 * Sun specific mechanisms to clean up resources associated with direct byte buffers.
 */
@SuppressWarnings("unchecked")
private static final Class<? extends ByteBuffer> SUN_DIRECT_BUFFER = (Class<? extends ByteBuffer>) lookupClassQuietly("sun.nio.ch.DirectBuffer");

private static final Method SUN_BUFFER_CLEANER;

private static final Method SUN_CLEANER_CLEAN;

static
{
    Method bufferCleaner = null;
    Method cleanerClean = null;
    try
    {
        // operate under the assumption that if the sun direct buffer class exists,
        // all of the sun classes exist
        if (SUN_DIRECT_BUFFER != null)
        {
            bufferCleaner = SUN_DIRECT_BUFFER.getMethod("cleaner", (Class[]) null);
            Class<?> cleanClazz = lookupClassQuietly("sun.misc.Cleaner");
            cleanerClean = cleanClazz.getMethod("clean", (Class[]) null);
        }
    }
    catch (Throwable t)
    {
        t.printStackTrace();
    }
    SUN_BUFFER_CLEANER = bufferCleaner;
    SUN_CLEANER_CLEAN = cleanerClean;
}

public static void releaseDirectByteBuffer(ByteBuffer buffer)
{
    if (SUN_DIRECT_BUFFER != null && SUN_DIRECT_BUFFER.isAssignableFrom(buffer.getClass()))
    {
        try
        {
            Object cleaner = SUN_BUFFER_CLEANER.invoke(buffer, (Object[]) null);
            SUN_CLEANER_CLEAN.invoke(cleaner, (Object[]) null);
        }
        catch (Throwable t)
        {
            logger.trace("Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t);
        }
    }
}

В существующих ответах на этот вопрос отсутствует много предостережений, например, требования при работе под JDK 9+, чтобы дескриптор модуля содержал requires jdk.unsupported, невозможность доступа MappedByteBuffer.cleaner()без обходных путей в JDK 16+ из-за применения строгой инкапсуляции требования при работе с SecurityManagerпод JDK 7-16 и т. д. Здесь я подробно расскажу:

/questions/45123168/kak-udalit-fajl-iz-pamyati-otobrazhennoj-s-pomoschyu-filechannel-v-java/45123187#45123187

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