Библиотеки JNI освобождают память при сборке мусора?
Я использую JCUDA и хотел бы знать, достаточно ли умны объекты JNI для освобождения, когда они собирают мусор? Я могу понять, почему это может не сработать во всех ситуациях, но я знаю, что это сработает в моей ситуации, поэтому мой следующий вопрос: как я могу это сделать? Есть ли "режим", который я могу установить? Нужно ли мне строить слой абстракции? Или, возможно, на самом деле ответ "нет, никогда не пытайся", так почему бы и нет?
РЕДАКТИРОВАТЬ: я имею в виду только нативные объекты, созданные с помощью JNI, а не объекты Java. Я знаю, что все объекты Java обрабатываются одинаково WRT мусора.
3 ответа
Обычно такие библиотеки не освобождают память из-за сборки мусора. В частности: JCuda не делает этого и не имеет опций или "режима", где это можно сделать.
Причина довольно проста: это не работает.
У вас часто будет такой шаблон:
void doSomethingWithJCuda()
{
CUdeviceptr data = new CUdeviceptr();
cuMemAlloc(data, 1000);
workWith(data);
// *(See notes below)
}
Здесь выделяется собственная память, а объект Java служит "дескриптором" этой собственной памяти.
На последней строке data
объект выходит из области видимости. Таким образом, он становится пригодным для сбора мусора. Однако есть две проблемы:
1. Сборщик мусора уничтожит только объект Java и не освободит память, выделенную для cuMemAlloc
или любой другой родной звонок.
Таким образом, вам, как правило, придется освободить собственную память, явно вызвав
cuMemFree(data);
перед выходом из метода.
2. Вы не знаете, когда объект Java будет собирать мусор или вообще будет ли он собираться мусором.
Распространенным заблуждением является то, что объект становится сборщиком мусора, когда он более недоступен, но это не обязательно так.
Как отметил в своем ответе bmargulies:
Одним из способов является наличие Java-объекта с финализатором, который делает необходимый вызов JNI для освобождения собственной памяти.
Это может выглядеть как жизнеспособный вариант просто переопределить finalize()
метод этих "обработать" объекты, и сделать cuMemFree(this)
позвони туда. Это было опробовано, например, авторами JavaCL (библиотека, которая также позволяет использовать GPU с Java, и, таким образом, концептуально чем-то похожа на JCuda).
Но это просто не работает: даже если Java-объект более недоступен, это не означает, что он будет сразу же удален.
Вы просто не знаете, когда finalize()
метод будет вызван.
Это может легко вызвать неприятные ошибки: если у вас есть 100 МБ памяти GPU, вы можете использовать 10 CUdeviceptr
объекты, каждый из которых выделяет 10 МБ. Ваша память GPU заполнена. Но для Java эти немногие CUdeviceptr
объекты занимают всего несколько байтов, а finalize()
метод может вообще не вызываться во время выполнения приложения, потому что JVM просто не нужно возвращать эти несколько байтов памяти. (Опуская обсуждения хакерских обходных путей здесь, например, звонить System.gc()
или так - суть: это не работает).
Итак, отвечая на ваш настоящий вопрос: JCuda - библиотека очень низкого уровня. Это означает, что у вас есть все возможности, а также все обязанности по ручному управлению памятью. Я знаю, что это "неудобно". Когда я начал создавать JCuda, я изначально задумывал его как низкоуровневый бэкэнд для объектно-ориентированной библиотеки-оболочки. Но создание надежного, стабильного и универсально применимого уровня абстракции для сложной библиотеки общего назначения, такой как CUDA, является сложной задачей, и я не осмелился заняться таким проектом - последнее, но не в последнюю очередь из-за сложностей, которые подразумевают... вещи как сборка мусора...
Java-объекты, созданные в JNI, равны всем остальным Java-объектам, и по мере их сбора собираются и уничтожаются. Чтобы такие объекты не разрушались слишком рано, мы часто используем функцию JNI env->NewGlobalRef()
(но его использование никоим образом не ограничено объектами, созданными в native).
С другой стороны, нативные объекты не подлежат сборке мусора.
Здесь есть два случая.
- Собственный код выделяет объекты Java. Эти объекты GC, как и все другие объекты Java. Если нативный дурак и содержит сильные ссылки, это может помешать GC.
- Нативный код выделяет Нативную память. GC ничего не знает об этом; дело за библиотекой, чтобы организовать ее освобождение. Одним из способов является наличие Java-объекта с финализатором, который делает необходимый вызов JNI для освобождения собственной памяти.