当前位置: 首页 > 知识库问答 >
问题:

返回给嵌入Java VM的程序的JNI本地引用的规则是什么?

冀永寿
2023-03-14

JNI文档描述了从JNI返回的对象的资源管理规则。下面是一段很好的概述:

JNI 将本机代码使用的对象引用分为两类:本地引用和全局引用。本地引用在本机方法调用期间有效,并在本机方法返回后自动释放。全局引用在被显式释放之前一直有效。

对象作为本地引用传递给本机方法。JNI函数返回的所有Java对象都是本地引用。JNI允许程序员从局部引用创建全局引用。期望Java对象的JNI函数接受全局和局部引用。本机方法可以返回对VM的本地或全局引用作为其结果。

本文档显然是针对使用JNI在本机代码中实现方法而编写的,但JNI也可以用于嵌入。嵌入的规则是什么?当JNI函数返回的“本机方法调用”是嵌入VM的封闭程序时,“本机法调用”,即程序,将永远不会返回VM。这种情况下的规则是什么?嵌入JNI的程序可以告诉JNI它可以释放以前返回的对象吗?

编辑:

下面是一个代码示例,我不知道如何根据文档处理从JNI返回的对象<代码>TestKlass。java定义了一个简单的java类<代码>运行。c启动一个嵌入式Java VM,并使用JNI加载TestKlassclass,然后运行TestKlass构造函数以获取 作业项目的资源管理规则是什么?Java VM什么时候会认为它可以安全地释放对象?

代码使用Xcheck:jni启动Java虚拟机,虚拟机不会打印任何错误,但这并不保证代码中不存在错误。如果本例中存在错误,我如何检测到它们?

TestKlass.java

public class TestKlass {
    public TestKlass() {
                System.out.println("Java: TestKlass::TestKlass()");
    }
}

运行. c

#include <jni.h>
#include <stdlib.h>

JNIEnv* create_vm(JavaVM** jvm)
{
    JNIEnv* env;
    JavaVMInitArgs vm_args;

    // For this example, TestKlass.java and run.c are assumed to live in and
    //     be compiled in the same directory, so '.' is added to the Java
    //     path.
    char opts0[] = "-Djava.class.path=.";
    char opts1[] = "-Xcheck:jni";
    JavaVMOption opts[2];
    opts[0].optionString = opts0;
    opts[1].optionString = opts1;

    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 2;
    vm_args.options = opts;
    vm_args.ignoreUnrecognized = 0;

    jint r = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
    if (r < 0 || !env) {
        printf("Unable to Launch JVM %d\n", r);
        abort();
    }
    printf("Launched JVM! :)\n");

    return env;
}

int main(int argc, char **argv)
{
    JavaVM *jvm;
    JNIEnv *env;
    env = create_vm(&jvm);
    if(env == NULL)
        return 1;

    jclass cls = (*env)->FindClass(env, "TestKlass");
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    jobject jobj = (*env)->NewObject(env, cls, mid);

    // (Assume that arbitrary JNI calls will be made after this point, the
    //     program will persist for a long time, and returned Java objects may
    //     be large.)
    // What are the rules for negotiating management of jobj with the Java VM?
    // Does the answer change if the object was returned from a
    //     non-constructor function?
    // Is there any way to use the JNI to tell the Java VM either that jobj
    //     can be freed here or that it must not be freed here?
    // Would DeleteLocalRef() be helpful here?

    // ...

    (*jvm)->DestroyJavaVM(jvm);
}

输出

Launched JVM! :)
Java: TestKlass::TestKlass()

我知道C,但我很久没有使用Java了。我主要从JNI文档开始工作。如果我遗漏了一些明显的东西,请在答案或评论中指出。

共有3个答案

昝欣可
2023-03-14

我在为Unity编写Java插件时对此进行了一些调查。我没有发现JVM在没有调用删除本地RefpopLocalFrame的情况下发布任何本地引用,使它们有效地全局引用,尽管它们可能不应该以这种方式使用。

下面是我的测试,我在Pixel 5上手动检查了Logcat的打印参考表输出。

using System;
using NUnit.Framework;
using UnityEngine;

/// <summary>
/// Tests various JNI behaviors.
/// </summary>
public class JniTest
{
    [Test]
    public void TestLocalReference()
    {
        CreateLocalReference(10000000);
        DumpReferenceTables();
    }
  
    [Test]
    public void TestGlobalReference()
    {
        CreateGlobalReference(10000000);
        DumpReferenceTables();
    }
  
    private void CreateLocalReference(int count)
    {
        for (int i = 0; i < count; i++)
        {
            AndroidJNI.NewString(i.ToString());
        }
    }
  
    private void CreateGlobalReference(int count)
    {
        for (int i = 0; i < count; i++)
        {
            IntPtr local = AndroidJNI.NewString(i.ToString());
            AndroidJNI.NewGlobalRef(local);
            AndroidJNI.DeleteLocalRef(local);
        }
    }
  
    private void DumpReferenceTables()
    {
        IntPtr debugClass = AndroidJNI.FindClass("android/os/Debug");
        IntPtr methodId = AndroidJNI.GetStaticMethodID(debugClass, "dumpReferenceTables", "()V");
        AndroidJNI.CallStaticVoidMethod(debugClass, methodId, null);
    }
}

有趣的是,Android上的Unity的最大计数与通常宣传的512本地和65536全球不同。我得到以下崩溃日志:

JNI ERROR (app bug): global reference table overflow (max=51200)global reference table dump:
    runtime.cc:677]   Last 10 entries (of 51200):
    runtime.cc:677]     51199: 0x12fc8f10 java.lang.String "50695"
    runtime.cc:677]     51198: 0x12fc8ef8 java.lang.String "50694"
    runtime.cc:677]     51197: 0x12fc8ee0 java.lang.String "50693"
    runtime.cc:677]     51196: 0x12fc8ec8 java.lang.String "50692"
    runtime.cc:677]     51195: 0x12fc8eb0 java.lang.String "50691"
    runtime.cc:677]     51194: 0x12fc8e98 java.lang.String "50690"
    runtime.cc:677]     51193: 0x12fc8e80 java.lang.String "50689"
    runtime.cc:677]     51192: 0x12fc8e68 java.lang.String "50688"
    runtime.cc:677]     51191: 0x12fc8e50 java.lang.String "50687"
    runtime.cc:677]     51190: 0x12fc8e38 java.lang.String "50686"
...

还有一个类似但令人惊讶的本地引用堆栈:

JNI ERROR (app bug): local reference table overflow (max=8388608)
...

其他手机/设置的限制可能不同,但教训是在从本机代码运行内容时始终清理本地引用。

我还测试了使用删除和弹出的性能权衡,发现清理2个引用时删除更快。这是运行100万次创建和删除的结果。

Refs/call | Push/Pop | DeleteLocalRef | Diff
0             474 ms          1078 ms    56%
1            5924 ms          6590 ms    10%
2           14986 ms         10716 ms   -40%
3           22581 ms         14618 ms   -54%
4           30045 ms         18418 ms   -63%

以下是测试的源代码:

using System;
using NUnit.Framework;
using UnityEngine;

public class JniTest
{
    [Test]
    public void TestPerformance()
    {
        var watch = new System.Diagnostics.Stopwatch();
        int iterations = 1000000;
        for (int i = 0; i < 5; i++)
        {
            watch.Start();
            MethodCallFrame(i, iterations);
            watch.Stop();
            long frameMs = watch.ElapsedMilliseconds;
            
            watch.Restart();
            MethodCallNoFrame(i, iterations);
            watch.Stop();
            long noFrameMs = watch.ElapsedMilliseconds;
            Debug.Log($"Test: {frameMs}, {noFrameMs}, {1 - (double)frameMs / noFrameMs}");
        }
    }

    private void MethodCallFrame(int args, int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            AndroidJNI.PushLocalFrame(0);
            for (int j = 0; j < args; j++)
            {
                AndroidJNI.NewString(j.ToString());
            }
            AndroidJNI.PopLocalFrame(IntPtr.Zero);
        }
    }
  
    private void MethodCallNoFrame(int argCount, int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            IntPtr[] args = new IntPtr[argCount];
            for (int j = 0; j < argCount; j++)
            {
                args[j] = AndroidJNI.NewString(j.ToString());
            }
            
            foreach (IntPtr ptr in args)
            {
                AndroidJNI.DeleteLocalRef(ptr);
            }
        }
    }
}
宁鹏程
2023-03-14

您必须在获得作业后立即将其转换为GlobalRef,并在完成后调用DeleteGlobalRef()

苏昊英
2023-03-14

如您所说,您不在< code>native方法中,因此在“返回”时不会进行清理。你的问题是关于返回前做好清洁工作。

要释放本地引用,您有两种选择:

  • 删除一个引用的LocalRef
  • 一组引用的PushLocalFrame

(我怀疑推送本地框架/Pop本地框架是清理本机方法的方式。

例子:

TestKlass.java

public class TestKlass {
    public TestKlass() {
        System.out.println("Java: TestKlass::TestKlass()");
    }

    public void finalize() {
        System.out.println("Java: TestKlass::finalize()");
    }

    public static void force_gc() {
        System.out.println("Java: TestKlass::force_gc()");

        System.gc();
        System.runFinalization();
    }
}

运行. c

#include <jni.h>
#include <stdlib.h>

JNIEnv* create_vm(JavaVM** jvm)
{
    JNIEnv* env;
    JavaVMInitArgs vm_args;

   // For this example, TestKlass.java and run.c are assumed to live in and
    //     be compiled the same directory, so '.' is added to the Java path.
    char opts0[] = "-Djava.class.path=.";
    char opts1[] = "-Xcheck:jni";
    JavaVMOption opts[2];
    opts[0].optionString = opts0;
    opts[1].optionString = opts1;

    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 2;
    vm_args.options = opts;
    vm_args.ignoreUnrecognized = 0;

    jint r = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
    if (r < 0 || !env) {
        printf("Unable to Launch JVM %d\n", r);
        abort();
    }
    printf("Launched JVM! :)\n");

    return env;
}

int main(int argc, char **argv)
{
    JavaVM *jvm;
    JNIEnv *env;
    env = create_vm(&jvm);
    if(env == NULL)
        return 1;

    jclass cls = (*env)->FindClass(env, "TestKlass");
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    jobject jobj = (*env)->NewObject(env, cls, mid);

    (*env)->DeleteLocalRef(env, jobj);
    jmethodID mid2 = (*env)->GetStaticMethodID(env, cls, "force_gc", "()V");
    (*env)->CallStaticVoidMethod(env, cls, mid2);

    (*jvm)->DestroyJavaVM(jvm);
}

输出

Launched JVM! :)
Java: TestKlass::TestKlass()
Java: TestKlass::force_gc()
Java: TestKlass::finalize()

移除对< code>DeleteLocalRef()的调用或对< code>force_gc()的调用会阻止< code>finalize()运行。

 类似资料:
  • JNI参考文献中说 “本地引用在本机方法调用期间有效。它们在本机方法返回后自动释放。 资料来源:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#global_local 我有点迷路了。根据上述内容,我必须显式调用NewGlobalRef并传递从调用NewObject返回的对象。我试过这个,似

  • 问题内容: JNI参考资料指出 “本地引用在本地方法调用期间有效。本地方法返回后,它们会自动释放。 来源:http : //docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#global_local 我有点迷路了。根据上述内容,我必须显式调用 NewGlobalRef 并将从调用返回的对象传递给 NewOb

  • 我最近对lambda可以分配给s这一事实感到惊讶。略有不同的意思是当指定返回时,lambda的返回值可能会被忽略,或者参数可能是中的引用,但lambda中的值。 请参阅此示例(ideone),其中我突出显示了我怀疑不兼容的内容。我认为返回值不是问题,因为您总是可以调用函数并忽略返回值,但从引用到值的转换对我来说看起来很奇怪: 小问题是:为什么这段代码要编译并运行?主要问题是:当与一起使用时,lam

  • 我读过几篇关于JNI本地和全球的参考文献。但是我找不到一个明确的答案,作为参数传递给JNI函数的Java对象是局部引用还是全局引用。我认为它应该是全球性的,但是有一个问题: 首先,我获取Java对象指针并保存它。然后,本机回调函数调用该对象的方法。回调函数是从单独的线程调用的。线程是使用AttachCurrentThread()创建的,因此JVM知道它。JNIEnv*变量也是有效的,对象没有被垃圾

  • 我正在学习/试验 Rust,在我发现的这门语言的所有优雅中,有一个特点让我感到困惑,似乎完全不合适。 Rust在进行方法调用时会自动取消对指针的引用。我做了一些测试来确定确切的行为: (游乐场) 所以,似乎或多或少: 编译器将插入调用方法所需的尽可能多的解引用运算符 编译器在解析使用<code>声明的方法时 确切的自动取消引用规则是什么?有人能给出这样一个设计决策的正式理由吗?

  • 我对消防商店的安全规则有疑问。 我的场景是这样的:一个Android应用程序,用户只会读取数据。他们不会保存,他们不会修改任何东西。他们不会注册。这只是数据读取。 就我而言,我需要写在那个集合中。我计划使用 API 密钥通过 cURL 来执行此操作。 我使用的规则如下: 我想阻止访问我的收藏,例如通过来自任何地方的 URL: < code > https://firestore . Google