我正在使用 JNI 在 C++ 中进行实验,并且对如何处理我在 C++ 中创建的用作 JNI 调用参数的 Java 对象有疑问。
采用这个非常简单的 java 类,带有字符串数组参数:
public class MyTest {
public static void main(String[] args) {
System.out.println("Hello, World in java");
int i;
for (i=0; i<args.length; i++)
System.out.println(args[i]);
}
}
我使用以下 JNI 代码从 C++ 调用它:
jmethodID mid3 = env->GetStaticMethodID(cls2, "main", "([Ljava/lang/String;)V"); // signature for String array arg.
if(mid3 == nullptr)
cerr << "ERROR: method not found !" << endl;
else {
jobjectArray arr = env->NewObjectArray(5, // constructs java array of 5
env->FindClass("java/lang/String"),
env->NewStringUTF("str")); // with this default value
env->SetObjectArrayElement( // change one of the array elements
arr, 1, env->NewStringUTF("MYOWNSTRING"));
env->CallStaticVoidMethod(cls2, mid3, arr); // call method
}
这个效果非常好。 但我不确定之后我必须如何处理 jarrayObject (及其包含的 java 字符串),以保持事物干净。
我的理解是JVM负责java对象。 但它如何知道我在 C++ 方面不再需要哪些对象呢? 我用谷歌搜索了一下,没有找到任何明确的解释。 我在 DeleteLocalRef()
的
JNI 规范中读到:
本地引用在本机方法调用期间有效。 它们会在本机方法返回后自动释放。
那么我应该为 jarrayObject (甚至它包含的每个 java 字符串)或其他一些清理函数调用
DeleteLocalRef()
吗?或者我可以假设 JVM 自己处理这个问题吗?
jni 设计规范(请参阅全局和本地参考部分)解释说:
但是,此原则仅适用于 Java 调用 C++ 函数的情况:当函数返回到 Java 时,本地引用将被释放(请参阅实现本地引用部分)。
如果您在不是从 Java 调用的 C++ 代码中创建对象或从 JNI 函数获取引用,则本地引用不会自动释放,从而导致内存泄漏。
所以你应该更好地使用
DeleteLocalRef()
来释放从 C++ 创建/获取但不再使用的 java 对象。
以下简单的Java代码分配大对象而不保留它们的引用,并调用一些内存检查:
class BigMemoryConsumer {
private char myTable[];
public BigMemoryConsumer () { // Allocate and use 1Mb
myTable = new char[1048576];
for (int i=0; i<1048576; i++)
myTable[i] = (char) (i % 256);
}
public static long showMem() { // Show memory statistics
int funit = 1024*1024;
String unit = " Mb";
Runtime runtime = Runtime.getRuntime();
System.gc(); // opportunity to run garbage collector (not guaranteed !)
long used = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used Memory: " + used / funit + unit
+ " ("+ (used*100/runtime.maxMemory())+"%)");
System.out.println("Free Memory: " + runtime.freeMemory() / funit + unit);
System.out.println("Total Memory: " + runtime.totalMemory() / funit + unit);
System.out.println("Max Memory: " + runtime.maxMemory() / funit + unit);
System.out.println("");
return used;
}
public static void main (String[] args) { // test in java
long lastmem = showMem();
for (int i=0; i<256; i++) {
BigMemoryConsumer m = new BigMemoryConsumer();
long mem = showMem();
if (mem<=lastmem) {
System.out.println ("Garbage collector freed some memory");
return;
}
else lastmem = mem;
}
}
}
当您直接从 Java(又名
main()
)运行此类时,您会注意到 Java 将非常快速地(在我的 64 位系统上的第二次或第三次迭代)运行垃圾收集器:对象 m 在每次迭代时都会重新初始化,这意味着以前创建的对象不再被引用。
现在我在
C++ 代码中重现了等效的
main()
,就在加载 JVM 并初始化 JNI 环境之后:
jclass cls = env->FindClass("BigMemoryConsumer");
jmethodID ctor = env->GetMethodID(cls, "<init>", "()V"); // find consructor
jmethodID show = env->GetStaticMethodID(cls, "showMem", "()J"); // find memShow()
jlong lastmem = 0;
vector<jobject> v;
for(int i = 0; i < 256; i++) {
jobject myo = env->NewObject(cls, ctor);
v.push_back(myo);
jlong mem = env->CallStaticLongMethod(cls, show);
if(mem <= lastmem) {
cout << "ATTENTION: garbage collector called as consumed java memory didn't increase after "<<i<<" iterations\n";
break;
}
else lastmem = mem;
//env->DeleteLocalRef(myo); ///!!!! <======= SEE TEXT
}
如果在没有
DeleteLocalRef()
的情况下运行代码,您会注意到消耗的内存不断增加:不会发生垃圾回收,因为 JVM 不知道不再使用对 C++ 请求的 java 对象的引用。
如果注释掉突出显示的行,
DeleteLocalRef()
将告诉 JVM C++ 代码中不再需要该对象,并且垃圾收集器的行为将与纯 java 示例中完全相同,在几次之后释放该对象迭代次数。