我要执行以下循环:
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
类实际上是我通过JNI调用API调用的Java库类的类包装器。我遇到的问题是,我想使用DeleteLocalRef
删除本地Java引用以防止内存泄漏(尤其重要,因为我将循环数千次)。
但是,由于在我的DeleteLocalRef
类析构函数中调用了state
,因此在作业的RHS返回时,对java jobject的引用将被破坏,从而使CURRENT_STATE无效,因为它包含对a的引用。作业已被删除。
如何避免这种情况?
@Wheezil
关于您的第一点-由于我正在使用调用API,即在C ++中创建虚拟机并调用Java函数,因此我认为我无需转换为全局引用(因为所有本地引用在JVM被保留之前都保持有效)销毁或直到线程分离。在这种情况下,我不会将线程分离并重新附加到JVM,因此永远不会删除本地引用。对我来说重要的是确保在JVM中删除本地引用。
关于第二点-我已经通过设置复制构造函数/赋值运算符= delete来禁止复制。我的问题更具体地是关于如何确保删除这些引用。
我的状态类如下:
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)为在PROPAGATE()函数中创建的state
实例调用析构函数。这将删除对jobject_state的本地引用,因此CURRENT_STATE对象不再具有有效的成员变量。
答案 0 :(得分:1)
从哪里开始... 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关于本地引用和全局引用,this link很好地描述了这一点:“当执行从创建本地引用的本机方法返回时,本地引用变得无效。因此,本机方法一定不能存储本地引用,并希望在后续调用中重用它。”您可以保留本地参考,但只能在严格的情况下进行。请注意,尤其是您不能将它们交给另一个线程,看来您并没有这样做。另一件事-可以激活的本地引用的总数受到限制。令人沮丧的是,这个限制没有得到很好的指定,但是它似乎是特定于JVM的。我建议保持谨慎,并始终转换为全局。
我以为我读过某个地方,您不需要删除jclass,因为FindClass()总是返回相同的东西,但是我很难验证这一点。在我们的代码中,我们也总是将jclass转换为全局引用。
ENV->DeleteLocalRef(this->jclass_state);
我必须承认对C ++ move语义的无知;只需确保未调用默认副本ctor,并且不会两次释放您的jobject_state即可。
this->jobject_state = state_to_move.jobject_state;
如果调用了您的move构造函数而不是复制构造函数或赋值,我不知道为什么您会看到删除临时目录的删除操作。正如我所说,我不是移动语义方面的专家。我一直让复制构造函数创建一个新的全局变量。参考。
答案 1 :(得分:0)
您不能这样做:
this->ENV = ENV;
您正在缓存从JVM传递给本机代码的JNIEnv
值。
你不能那样做。
好的,有个例子,您可以在某些实例中使用,但是只能在单个线程上工作,因此当同一线程使用JNIEnv *
值时,无需缓存它以供以后参考。
从您所发布内容的复杂性来看,我严重怀疑您能否保证您的本机代码每次都被同一线程调用。
每次从JVM调用本机代码时,您都会得到一个JNIenv *
传递,因此缓存JNIEnv
值几乎没有任何意义。
IMO,您正在使本机代码过于复杂。无需缓存和跟踪所有这些引用。看起来您正在尝试使本机C ++对象与Java对象保持同步。为什么?如果本机代码需要访问Java对象,只需在Java调用中将该对象传递给本机代码即可。