删除对象分配JNI调用API中的jobject引用

时间:2018-11-06 17:27:10

标签: java c++ reference java-native-interface invocation

我要执行以下循环:

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_stateCURRENT_STATE成员。

2)为在PROPAGATE()函数中创建的state实例调用析构函数。这将删除对jobject_state的本地引用,因此CURRENT_STATE对象不再具有有效的成员变量。

2 个答案:

答案 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调用中将该对象传递给本机代码即可。