Как собрать мусор прямым буфером в Java - PullRequest
27 голосов
/ 06 декабря 2009

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

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

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

java.lang.OutOfMemoryError: Direct buffer memory

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

buff.clear();

и

System.gc();

не работают.

Ответы [ 6 ]

16 голосов
/ 19 ноября 2011

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

}
15 голосов
/ 06 декабря 2009

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

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

12 голосов
/ 06 декабря 2009

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

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

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

5 голосов
/ 06 декабря 2009

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

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

2 голосов
/ 08 октября 2013

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

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);
        }
    }
}
1 голос
/ 02 января 2014

Пока вы полагаетесь на конкретную реализацию 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);
        }
    }
}
...