Подтвердить что ты не робот

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

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

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

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

java.lang.OutOfMemoryError: Direct buffer memory

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

buff.clear();

а также

System.gc();

не работай.

4b9b3361

Ответ 1

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

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

Ответ 2

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);

}

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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);
        }
    }
}

Ответ 6

Пока вы полагаетесь на конкретную реализацию солнца (оракула), лучший выбор, чем попытка изменить видимость 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);
        }
    }
}