jarrayObject(字符串数组)在 JNI 调用中使用后是否应被删除/释放?

问题描述 投票:0回答:1

我正在使用 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 自己处理这个问题吗?

java c++ memory-management java-native-interface
1个回答
2
投票

jni 设计规范(请参阅全局和本地参考部分)解释说:

  • 传递给本机方法的所有引用以及 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 示例中完全相同,在几次之后释放该对象迭代次数。

© www.soinside.com 2019 - 2024. All rights reserved.