所以我在 Java 中有以下 JNIManager 类。在这个类中,如您所见,我定义了一个名为 setUpBackGround() 的 native 方法;
public class JNIManager{
public native void setUpBackground();
public void messageMe(byte[] byteArray);
}
然后我用 native (C) 代码实现了另一个类。我们称这个类为背景类。此类执行一些后台工作并调用 JNIManager 类的 messageMe() 方法,向其传递一个字节 []。
class Background{
JNIEnv* mJNIEnv;
jbyteArray mArray;
jobject mJObject;
Background(JNIEnv * env, jobject jObject){
mArray = env->NewByteArray(1040);
mJNIEnv = env;
mJObject = jObject;
}
virtual ~Background(){
mJNIEnv->DeleteLocalRef(mArray); //is this necessary?
}
void someMethod(){
jclass manager = mJNIEnv->GetObjectClass(mJObject);
jmethodID method = mJNIEnv->GetMethodID(manager, "messageMe", "([B)V");
mJNIEnv->CallVoidMethod(mJObject, method, mArray);
mJNIEnv->DeleteLocalRef(manager); // is this necessary?
}
}
现在,在 native 方法 setUpBackground 中,我执行以下操作,
JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_setUpBackground
(JNIEnv * env, jobject jo){
Background* back = new Background(env,jo);
return 0;
}
最后,在另一个类的方法中,我创建了一个 JNIManager 实例并调用本地方法 setUpBackground()。
otherMethod(){
JNIManager jniManager = new JNIManager();
jniManager.setUpBackground();
}
我对整个设置有 2 个问题。
1) 当 jniManager 在上述方法结束时超出范围时,我使用“new”关键字动态创建的 Background 类是否会自动被垃圾回收?我认为它不会并且会导致内存泄漏。这样对吗?如果是这样,我该怎么做才能纠正它?
2) 是否需要调用 DeleteLocalRef() 来避免内存泄漏,或者一旦它们不再使用,JVM 是否会负责删除它们?
-------------------------------------------- --------------------------------------------
更新 - 根据 nneonneo 的回答
public class JNIManager{
private long nativeHandle;
public JNIManager(){
nativeHandle = setUpBackground();
}
public native long setUpBackground();
public native void releaseBackground(long handle);
public void messageMe(byte[] byteArray) {//do some stuff};
}
更新背景类
class Background{
public:
JavaVM* mJvm;
JNIEnv* mJNIEnv;
jobject mJObject;
jbyteArray mArray;
int file;
Background(JNIEnv * env, jobject jObject){
env->GetJavaVM(&mJvm);
attachToThread();
mJObject = env->NewGlobalRef(jObject);
mArray = env->NewByteArray(1040);
file = 1; //Does this need to be a globalRef ?
}
void destroy(){
mJNIEnv->DeleteGlobalRef(mArray);
mJNIEnv->DeleteGlobalRef(mJObject);
mJvm = NULL;
mJNIEnv = NULL;
file = 0;
}
void someMethod(){
attachToThread();
jclass manager = mJNIEnv->GetObjectClass(mJObject);
jmethodID method = mJNIEnv->GetMethodID(manager, "messageMe", "([B)V");
mJNIEnv->CallVoidMethod(mJObject, method, mArray);
mJNIEnv->DeleteLocalRef(manager);
detachFromThread();
}
void attachToThread(){
mJvm->AttachCurrentThread(&mJNIEnv, NULL);
}
void detachFromThread(){
mJvm->DetachCurrentThread();
}
}
更新的原生方法
JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_setUpBackground
(JNIEnv * env, jobject jo){
Background* back = new Background(env,jo);
return reinterpret_cast<jlong>(back);
}
JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_releaseBackground
(JNIEnv * env, jobject jo, jlong handle){
Background* back = reinterpret_cast<Background* back>(handle);
back.destroy();
delete back;
}
更新了其他方法
otherMethod(){
JNIManager jniManager = new JNIManager();
//Do some stuff...
jniManager.releaseBackground();
}
我不确定第一点。
“JNIEnvs 在 JNI 调用之间可能不会保持相同(特别是,两个不同的线程将具有不同的 JNIEnvs)”。
这是否意味着一旦 setupBackground() JNI 调用返回,当前线程就被删除了? 既然Background类是在这个线程中创建的,那么这个类的方法(比如someMethod())不会在同一个线程中运行吗?如果不是,attachToThread() 方法是否定义了获取当前线程并使用其 JNIEnv* 的正确方法?
我基本上需要创建的背景对象在 JNIManager 的整个生命周期中都存在。然后偶尔调用 Background 对象的 someMethod() (通过一些外部类)然后这将调用 messageMe() 中的方法 JNIManager 类(如代码中所示)。
最佳答案
你做错了几件事。
您不能在 JNI 方法返回后存储对
JNIEnv
的引用。JNIEnv
在 JNI 调用之间可能不会保持相同(特别是,两个不同的线程将具有不同的JNIEnv
,并且 Java 类终结器可能在单独的线程上运行)。您不能在 JNI 方法返回后存储 LocalRef。从 JNI 函数返回时,所有本地引用都会被删除。如果您需要保留对对象的引用,请使用全局引用(然后您负责删除)。
mArray
和mJObject
都必须是全局引用。是的,你漏水了。您不会在任何地方
删除
返回
,事实上您甚至不会在任何地方存储它的地址,所以它会泄漏。如果您希望Background
成为单例类,那么实际上使用适当的单例模式来实现它。如果您打算将Background
的生命周期绑定(bind)到JNIManager
的生命周期,那么您必须向JNIManager
添加适当的终结代码以销毁Background
实例(并将Background
实例存储在某处,例如在JNIManager
类实例上)。
关于java - 如何构造 JNI 调用以避免内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24133570/