JNI文档描述了从JNI返回的对象的资源管理规则。下面是一段很好的概述:
JNI 将本机代码使用的对象引用分为两类:本地引用和全局引用。本地引用在本机方法调用期间有效,并在本机方法返回后自动释放。全局引用在被显式释放之前一直有效。
对象作为本地引用传递给本机方法。JNI函数返回的所有Java对象都是本地引用。JNI允许程序员从局部引用创建全局引用。期望Java对象的JNI函数接受全局和局部引用。本机方法可以返回对VM的本地或全局引用作为其结果。
本文档显然是针对使用JNI在本机代码中实现方法而编写的,但JNI也可以用于嵌入。嵌入的规则是什么?当JNI函数返回的“本机方法调用”是嵌入VM的封闭程序时,“本机法调用”,即程序,将永远不会返回VM。这种情况下的规则是什么?嵌入JNI的程序可以告诉JNI它可以释放以前返回的对象吗?
编辑:
下面是一个代码示例,我不知道如何根据文档处理从JNI返回的对象<代码>TestKlass。java定义了一个简单的java类<代码>运行。c启动一个嵌入式Java VM,并使用JNI加载TestKlass
class,然后运行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文档开始工作。如果我遗漏了一些明显的东西,请在答案或评论中指出。
我在为Unity编写Java插件时对此进行了一些调查。我没有发现JVM在没有调用删除本地Ref
或popLocalFrame
的情况下发布任何本地引用,使它们有效地全局引用,尽管它们可能不应该以这种方式使用。
下面是我的测试,我在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);
}
}
}
}
您必须在获得作业后立即将其转换为GlobalRef
,并在完成后调用DeleteGlobalRef()
。
如您所说,您不在< 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
以上两段代码,一个返回String的可变引用,一个返回不可变引用,为什么返回可变引用的闭包实现了FnOnce,返回不可变引用的闭包只实现了Fn呢? rust返回引用时,遵循什么规则?和my_s_ref本身有关还是my_string有关?
我最近对lambda可以分配给s这一事实感到惊讶。略有不同的意思是当指定返回时,lambda的返回值可能会被忽略,或者参数可能是中的引用,但lambda中的值。 请参阅此示例(ideone),其中我突出显示了我怀疑不兼容的内容。我认为返回值不是问题,因为您总是可以调用函数并忽略返回值,但从引用到值的转换对我来说看起来很奇怪: 小问题是:为什么这段代码要编译并运行?主要问题是:当与一起使用时,lam
我读过几篇关于JNI本地和全球的参考文献。但是我找不到一个明确的答案,作为参数传递给JNI函数的Java对象是局部引用还是全局引用。我认为它应该是全球性的,但是有一个问题: 首先,我获取Java对象指针并保存它。然后,本机回调函数调用该对象的方法。回调函数是从单独的线程调用的。线程是使用AttachCurrentThread()创建的,因此JVM知道它。JNIEnv*变量也是有效的,对象没有被垃圾
我对消防商店的安全规则有疑问。 我的场景是这样的:一个Android应用程序,用户只会读取数据。他们不会保存,他们不会修改任何东西。他们不会注册。这只是数据读取。 就我而言,我需要写在那个集合中。我计划使用 API 密钥通过 cURL 来执行此操作。 我使用的规则如下: 我想阻止访问我的收藏,例如通过来自任何地方的 URL: < code > https://firestore . Google