Управление памятью JNI с использованием Invocation API - PullRequest
6 голосов
/ 18 октября 2008

Когда я создаю объект Java с использованием методов JNI, чтобы передать его в качестве параметра методу Java, который я вызываю с помощью API вызова JNI, как мне управлять его памятью?

Вот с чем я работаю:

У меня есть объект C, у которого есть деструктор, более сложный, чем free(). Этот объект C должен быть связан с объектом Java, и как только приложение будет завершено с объектом Java, мне больше не понадобится объект C.

Я создаю объект Java примерно так (проверка ошибок исключена для ясности):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

Итак, теперь, когда я закончил с instance, что мне с ним делать? В идеале я хотел бы оставить сборку мусора до виртуальной машины; когда это будет сделано с instance, было бы замечательно, если бы он также вызывал c_object_destroy() на указателе, который я ему предоставил. Это возможно?

Отдельный, но связанный вопрос касается области сущностей Java, которые я создаю в таком методе; я должен вручную отпустить, скажем, class, constructor или method выше? Документ JNI удручающе неопределен (по моему мнению) относительно правильного управления памятью.

Ответы [ 4 ]

10 голосов
/ 19 октября 2008

Спецификация JNI охватывает вопрос о том, кто «владеет» объектами Java, созданными в методах JNI здесь . Необходимо различать локальные и глобальные ссылки.

Когда JVM выполняет вызов JNI для собственного кода, он создает реестр для отслеживания всех объектов, созданных во время вызова. Любой объект, созданный во время собственного вызова (т. Е. Возвращенный из функции интерфейса JNI), добавляется в этот реестр. Ссылки на такие объекты известны как локальные ссылки . Когда нативный метод возвращается в JVM, все локальные ссылки, созданные во время нативного вызова метода, уничтожаются. Если вы выполняете вызовы обратно в JVM во время вызова собственного метода, локальная ссылка все еще остается активной, когда управление возвращается обратно к собственному методу. Если JVM, вызванная из собственного кода, выполняет обратный вызов в нативный код, создается новый реестр локальных ссылок и применяются те же правила.

(На самом деле, вы можете реализовать свой собственный исполняемый файл JVM (например, java.exe), используя интерфейс JNI, создав JVM (таким образом получив указатель JNIEnv *), просматривая класс, заданный в командной строке, и вызывая метод main () для него.)

Все ссылки, возвращаемые методами интерфейса JNI, являются локальными . Это означает, что в нормальных условиях вам не нужно вручную освобождать ссылки, возвращаемые методами JNI, поскольку они уничтожаются при возврате в JVM. Иногда вы все еще хотите уничтожить их «преждевременно», например, когда у вас много локальных ссылок, которые вы хотите удалить, прежде чем вернуться в JVM.

Глобальные ссылки создаются (из локальных ссылок) с помощью NewGlobalRef (). Они добавляются в специальный реестр и должны быть удалены вручную. Глобальные ссылки используются только для объекта Java, на который собственный код должен содержать ссылку на несколько вызовов JNI, например, если у вас есть события, запускающие собственный код, которые должны быть переданы обратно в Java. В этом случае код JNI должен хранить ссылку на объект Java, который должен получить событие.

Надеюсь, это немного прояснит проблему управления памятью.

5 голосов
/ 18 октября 2008

Существует несколько стратегий восстановления собственных ресурсов (объектов, файловых дескрипторов и т. Д.)

  1. Вызвать метод JNI во время finalize (), который освобождает ресурс. Некоторые люди рекомендуют против реализации finalize , и в принципе вы не можете быть уверены, что ваш родной ресурс когда-либо будет освобожден. Для таких ресурсов, как память, это, вероятно, не проблема, но если у вас есть, например, файл, который необходимо очистить в предсказуемое время, то finalize (), вероятно, не очень хорошая идея.

  2. Вручную вызвать метод очистки. Это полезно, если у вас есть момент времени, когда вы знаете, что ресурс должен быть очищен. Я использовал этот метод, когда у меня был ресурс, который должен был быть освобожден перед выгрузкой DLL в коде JNI. Чтобы впоследствии можно было перезагружать DLL, я должен был быть уверен, что объект действительно был освобожден, прежде чем пытаться выгружать DLL. Используя только finalize (), я бы не получил это гарантировано. Это может быть объединено с (1), чтобы позволить ресурсу быть распределенным или во время finalize () или в вызываемом вручную методе очистки. (Вам, вероятно, нужна каноническая карта WeakReferences, чтобы отследить, для каких объектов должен быть вызван их метод очистки.)

  3. Предположительно, PhantomReference также может быть использован для решения этой проблемы, но я не уверен, как именно это решение будет работать.

На самом деле, я не согласен с вами по поводу документации JNI. Я нахожу спецификацию JNI исключительно ясной по большинству важных вопросов, даже если бы разделы по управлению локальными и глобальными ссылками могли быть более детальными.

1 голос
/ 19 октября 2008

Re: «Отдельный, но связанный вопрос» ... вам не нужно вручную освобождать jclass, jfieldID и jmethodID при использовании их в «локальном» контексте. Любые фактические ссылки на объекты, которые вы получаете (не jclass, jfieldID, jmethodID), должны быть освобождены с DeleteLocalRef.

0 голосов
/ 18 октября 2008

GC будет собирать ваш экземпляр, но он не будет автоматически освобождать память кучи не-Java, выделенную в собственном коде. В вашем классе должен быть явный метод для освобождения экземпляра c_object.

Это один из случаев, когда я бы рекомендовал использовать финализатор, проверяющий, выпущен ли c_object, и выпускающий его, регистрируя сообщение, если это не так.

Полезный метод - создать экземпляр Throwable в конструкторе класса Java и сохранить его в поле (или просто инициализировать поле в строке). Если финализатор обнаружит, что класс не был правильно размещен, он напечатает трассировку стека, точно указав стек выделения.

Рекомендуется избегать прямого JNI и использовать gluegen или Swig (оба генерируют код и могут быть статически связаны).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...