Я хочу выполнить следующий цикл:
for(absolute_date CURRENT_DATE = START_DATE; CURRENT_DATE.COMPARE_TO(END_DATE) <= 0; CURRENT_DATE.SHIFT_SELF(TIMESTEP))
{
CURRENT_STATE = Propagator.PROPAGATE(CURRENT_DATE);
}
куда CURRENT_STATE
является объектом класса state
, propagator::PROPAGATE()
это метод, который возвращает объект класса state
,
мой state
класс — это по сути класс-оболочка для класса библиотеки Java, который я вызываю через API вызова JNI. Проблема у меня в том, что я хочу удалить локальные ссылки Java сDeleteLocalRef
для предотвращения утечек памяти (особенно важно, так как я буду повторять много тысяч раз).
Тем не менее, так как DeleteLocalRef
называется в моем state
деструктор класса, ссылка на java jobject уничтожается при возврате RHS присваивания, что делает CURRENT_STATE недействительным, так как содержит ссылку на заданный объект, который был удален.
Как мне избежать этого?
@Wheezil
Что касается вашего первого пункта — поскольку я использую API вызова, т.е. создаю виртуальную машину внутри C ++ и вызываю функции Java, я не думаю, что мне нужно преобразовывать в глобальные ссылки (поскольку все локальные ссылки остаются действительными до тех пор, пока JVM не будет уничтожена или до нить оторвана). В этом случае я не отсоединяю и не присоединяю потоки к JVM, поэтому локальные ссылки никогда не удаляются. Для меня важно убедиться, что локальные ссылки удаляются в JVM.
Что касается второго пункта — я уже запретил копирование, установив свои конструкторы копирования / операторы присваивания = удалить. Моя проблема более конкретно о том, как обеспечить удаление этих ссылок.
Мой государственный класс выглядит так:
state::state(JNIEnv* ENV)
{
this->ENV = ENV;
this->jclass_state = ENV->FindClass("/path/to/class");
this->jobject_state = nullptr;
}
state::~state()
{
if(DOES_JVM_EXIST())
{
ENV->DeleteLocalRef(this->jclass_state);
ENV->DeleteLocalRef(this->jobject_state); //PROBLEMATIC
}
}
state::state(state&& state_to_move)
{
this->ENV = state_to_move.ENV;
//move jobjects from mover => new object
this->jobject_state = state_to_move.jobject_state;
this->jclass_state = state_to_move.jclass_state;
}
state& state::operator =(state&& state_to_move)
{
this->ENV = state_to_move.ENV;
//move jobjects from mover => current object
this->jobject_state= state_to_move.jobject_state;
this->jclass_state = state_to_move.jclass_state;
return *this;
}
Чтобы описать проблему, с которой я сталкиваюсь, более подробно: propagator::PROPAGATE()
метод возвращает state
объект по значению (в настоящее время выделен стек). Как только эта функция возвращается, происходит следующее:
1) Оператор назначения перемещения вызывается. Это устанавливает jobject_state
а также jclass_state
члены в CURRENT_STATE
объект.
2) Деструктор вызывается для state
экземпляр, созданный в функции PROPAGATE (). Это удаляет локальную ссылку на jobject_state, и, таким образом, объект CURRENT_STATE больше не имеет допустимой переменной-члена.
С чего начать … JNI невероятно привередливый и неумолимый, и если вы не сделаете все правильно, он взорвется. Ваше описание довольно тонкое (пожалуйста, предоставьте более подробную информацию, если это не поможет), но я могу сделать хорошее предположение. Есть несколько проблем с вашим подходом. Вы, вероятно, делаете что-то вроде этого:
struct state {
state(jobject thing_) : thing(thing_) {}
~state() { env->DeleteLocalRef(thing); }
jobject thing;
}
Первая проблема заключается в том, что хранение местных ссылок опасно. Вы не можете висеть на них за пределами текущего кадра JNI. Так что конвертируйте их в глобальные
struct state {
state(jobject thing_) : thing(env->NewGlobalRef(thing_)) {
env->DeleteLocaLRef(thing_);
}
~state() { env->DeleteGlobalRef(thing); }
jobject thing;
}
Вторая проблема заключается в том, что jobject в основном похож на старый C ++ auto_ptr<> — действительно небезопасно, потому что копирование его приводит к опасным указателям и двойным освобождениям. Таким образом, вам нужно либо запретить копирование состояния и, возможно, только передать состояние *, либо создать конструктор копирования, который работает:
state(const state& rhs) thing(env->NewGlobalRef(rhs.thing)) {}
Это должно по крайней мере вывести вас на правильный путь.
ОБНОВЛЕНИЕ: Ddor, относительно местных и глобальных ссылок, эта ссылка хорошо описывает это: «Локальные ссылки становятся недействительными, когда выполнение возвращается из собственного метода, в котором создана локальная ссылка. Поэтому нативный метод не должен хранить локальную ссылку и ожидать ее повторного использования в последующих вызовах». Вы Можно хранить местные ссылки, но только при строгих обстоятельствах. Обратите внимание, что, в частности, вы не можете передать их другому потоку, что, по-видимому, вы не делаете. Другое дело — существуют ограничения на общее количество локальных ссылок, которые могут быть активными. Этот предел разочаровывает, но он кажется специфичным для JVM. Я советую осторожность и всегда переходить на глобальный.
Я думал, что где-то читал, что вам не нужно удалять jclass, потому что FindClass () всегда возвращает одно и то же, но мне трудно это проверить. В нашем коде мы также всегда преобразуем jclass в глобальную ссылку.
ENV->DeleteLocalRef(this->jclass_state);
Я должен признать, что не знает о семантике перемещения C ++; просто убедитесь, что копия по умолчанию ctor не вызывается и ваше jobject_state не освобождается дважды.
this->jobject_state = state_to_move.jobject_state;
Если ваш конструктор перемещения вызывается вместо конструктора копирования или присваивания, я не знаю, почему вы видите удаление при уничтожении временного объекта. Как я уже сказал, я не эксперт по семантике перемещения. У меня всегда был конструктор копирования, создающий новый глобал. ссылка.
Ты не сможешь это сделать:
this->ENV = ENV;
Вы кешируете JNIEnv
значение передается в собственный код из JVM.
Вы не можете сделать это.
Хорошо, чтобы быть педантичным, есть некоторые случаи, когда вы можете, но это может работать только из одного потока, поэтому нет необходимости кэшировать JNIEnv *
значение для последующего использования, когда оно используется тем же потоком.
Из-за сложности того, что вы опубликовали, я серьезно сомневаюсь, что вы можете гарантировать, что ваш нативный код будет вызываться одним и тем же потоком каждый раз.
Вы прошли JNIenv *
каждый раз, когда нативный код вызывается из JVM, так что практически нет смысла кэшировать JNIEnv
значение.
ИМО, вы делаете свой родной код слишком сложным. Нет необходимости кэшировать и отслеживать все эти ссылки. Похоже, вы пытаетесь синхронизировать нативный объект C ++ с Java-объектом. Зачем? Если нативный код нуждается в доступе к Java-объекту, просто передайте этот объект при вызове из Java в нативный код.