JNI Прикрепить/отделить управление памятью потоков
У меня есть обратный вызов JNI:
void callback(Data *data, char *callbackName){
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
/* start useful code*/
/* end useful code */
jvm->DetachCurrentThread();
}
Когда я запускаю его так (пустой полезный код), я получаю утечку памяти. Если я прокомментирую весь метод, утечки нет. Каков правильный способ прикрепления/отсоединения потоков?
Мои приложения обрабатывают звуковые данные в реальном времени, поэтому потоки, ответственные за обработку данных, должны быть выполнены как можно скорее, чтобы быть готовыми к другой партии. Поэтому для этих обратных вызовов я создаю новые потоки. Есть десятки или даже сотни из них каждую секунду, они присоединяются к JVM, вызывают функцию обратного вызова, которая перерисовывает график, снимает и умирает. Это правильный способ сделать это? Как обрабатывать утечку памяти?
EDIT: typo
OK Я создал нужный код:
package test;
public class Start
{
public static void main(String[] args) throws InterruptedException{
System.loadLibrary("Debug/JNITest");
start();
}
public static native void start();
}
и
#include <jni.h>
#include <Windows.h>
#include "test_Start.h"
JavaVM *jvm;
DWORD WINAPI attach(__in LPVOID lpParameter);
JNIEXPORT void JNICALL Java_test_Start_start(JNIEnv *env, jclass){
env->GetJavaVM(&jvm);
while(true){
CreateThread(NULL, 0, &(attach), NULL, 0, NULL);
Sleep(10);
}
}
DWORD WINAPI attach(__in LPVOID lpParameter){
JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);
jvm->DetachCurrentThread();
return 0;
}
и когда я запускаю профилировщик VisualJM, я получаю обычный пилообразный рисунок, без утечки. Использование кучи достигло максимума около 5 МБ. Тем не менее, наблюдение за проводником процессов действительно показывает какое-то странное поведение: память медленно поднимается и поднимается, 4 К в секунду в течение минуты или около того, а затем внезапно все это выделяет память. Эти капли не соответствуют сбору мусора (они встречаются реже и освобождают меньше памяти, чем пильные диски в профилировщике).
Так что я лучше всего то, что это некоторая работа с ОС, обрабатывающая десятки тысяч milisecond-живых потоков. У кого-то у гуру есть объяснение?
Ответы
Ответ 1
Я решил проблему. Это были болтающиеся локальные ссылки в коде JNI, который я не уничтожал. Каждый обратный вызов создавал бы новую локальную ссылку, что приводило бы к утечке памяти. Когда я преобразовал локальную ссылку в глобальную, я могу ее повторно использовать, проблема исчезла.
Ответ 2
Несколько вопросов о переходе на Java из собственного кода:
- AttachCurrentThread следует вызывать только в том случае, если jvm- > GetEnv() возвращает нулевое значение. Обычно он не работает, если поток уже подключен, но вы можете сэкономить некоторые накладные расходы.
- DetachCurrentThread следует вызывать только при вызове AttachCurrentThread.
- Избегайте отсоединения, если вы ожидаете, что в будущем вы будете вызваны в тот же поток.
В зависимости от поведения нативного кода вашего кода вы можете избежать отсоединения и вместо этого хранить ссылки на все собственные потоки для удаления при завершении (если вам это нужно, вы можете полагаться на выключение приложения для очистки вверх).
Если вы постоянно прикрепляете и отключаете собственные потоки, виртуальная машина должна постоянно связывать (часто одни и те же) потоки с объектами Java. Некоторые виртуальные машины могут повторно использовать потоки или временно кэшировать сопоставления для повышения производительности, но вы получите лучшее и более предсказуемое поведение, если вы не будете полагаться на виртуальную машину, чтобы сделать это за вас.