Как собрать мусор прямым буфером 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 и т. д. Здесь я подробно расскажу: