java 使用调用 API 的 JNI 内存管理
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/214699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JNI memory management using the Invocation API
提问by Chris R
When I'm building a java object using JNI methods, in order to pass it in as a parameter to a java method I'm invoking using the JNI invocation API, how do I manage its memory?
当我使用 JNI 方法构建 java 对象时,为了将它作为参数传递给我使用 JNI 调用 API 调用的 java 方法,我该如何管理它的内存?
Here's what I am working with:
这是我正在使用的内容:
I have a C object that has a destructor method that is more complex that free(). This C object is to be associated with a Java object, and once the application is finished with the Java object, I have no more need for the C object.
我有一个 C 对象,它有一个更复杂的析构函数方法free()。这个 C 对象将与一个 Java 对象相关联,一旦应用程序完成了 Java 对象,我就不再需要 C 对象了。
I am creating the Java object like so (error checking elided for clarity):
我正在像这样创建 Java 对象(为清楚起见,省略了错误检查):
c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);
method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);
So, now that I'm done with instance, what do I do with it? Ideally, I'd like to leave the garbage collection up to the VM; when it's done with instanceit would be fantastic if it also called c_object_destroy()on the pointer I provided to it. Is this possible?
那么,既然我已经完成了instance,我该怎么办呢?理想情况下,我希望将垃圾收集留给 VM;当它完成时instance,如果它还调用c_object_destroy()我提供给它的指针,那就太棒了。这可能吗?
A separate, but related question has to do with the scope of Java entities that I create in a method like this; do I have to manually release, say, class, constructor, or methodabove? The JNI doc is frustratingly vague (in my judgement) on the subject of proper memory management.
一个单独但相关的问题与我在这样的方法中创建的 Java 实体的范围有关;我必须手动释放,也就是说,class,constructor,或method以上?JNI 文档在适当的内存管理主题上令人沮丧地含糊不清(在我看来)。
采纳答案by JesperE
There are a couple of strategies for reclaiming native resources (objects, file descriptors, etc.)
有几种回收本机资源(对象、文件描述符等)的策略。
Invoke a JNI method during finalize() which frees the resource. Some people recommend against implementing finalize, and basically you can't really be sure that your native resource is ever freed. For resources such as memory this is probably not a problem, but if you have a file for example which needs to be flushed at a predictable time, finalize() probably not a good idea.
Manually invoke a cleanup method. This is useful if you have a point in time where you know that the resource must be cleaned up. I used this method when I had a resource which had to be deallocated before unloading a DLL in the JNI code. In order to allow the DLL to later be reloaded, I had to be sure that the object was really deallocated before attempting to unload the DLL. Using only finalize(), I would not have gotten this guaranteed. This can be combined with (1) to allow the resource to be allocated either during finalize() or at the manually called cleanup method. (You probably need a canonical map of WeakReferences to track which objects needs to have their cleanup method invoked.)
Supposedly the PhantomReferencecan be used to solve this problem as well, but I'm not sure exactly how such a solution would work.
在释放资源的 finalize() 期间调用 JNI 方法。有些人建议不要实现 finalize,基本上你不能真正确定你的原生资源是否被释放过。对于诸如内存之类的资源,这可能不是问题,但是例如,如果您有一个文件需要在可预测的时间刷新,则 finalize() 可能不是一个好主意。
手动调用清理方法。如果您知道必须清理资源的某个时间点,这将非常有用。当我有一个在 JNI 代码中卸载 DLL 之前必须释放的资源时,我使用了这种方法。为了允许稍后重新加载 DLL,在尝试卸载 DLL 之前,我必须确保该对象确实已释放。只使用 finalize(),我不会得到这个保证。这可以与 (1) 结合使用,以允许在 finalize() 期间或在手动调用的清理方法中分配资源。(您可能需要 WeakReferences 的规范映射来跟踪哪些对象需要调用其清理方法。)
据说PhantomReference也可以用来解决这个问题,但我不确定这样的解决方案究竟是如何工作的。
Actually, I have to disagree with you on the JNI documentation. I find the JNI specificationexceptionally clear on most of the important issues, even if the sections on managing local and global references could have been more elaborated.
实际上,我不得不在 JNI 文档上不同意您的意见。我发现JNI 规范在大多数重要问题上都非常清楚,即使有关管理本地和全局引用的部分本来可以更详细说明。
回答by JesperE
The JNI spec covers the issue of who "owns" Java objects created in JNI methods here. You need to distinguish between localand globalreferences.
JNI的规范涵盖的谁“拥有”这个问题的Java对象JNI方法创建这里。您需要区分本地引用和全局引用。
When the JVM makes a JNI call out to native code, it sets up a registry to keep track of all objects created during the call. Any object created during the native call (i.e. returned from a JNI interface function) are added to this registry. References to such objects are known as local references. When the native method returns to the JVM, all local references created during the native method call are destroyed. If you're making calls back into the JVM during a native method call, the local reference will still be alive when control returns back to the native method. If the JVM invoked from native code makes another call back into the native code, a new registry of local references is created, and the same rules apply.
当 JVM 向本机代码发出 JNI 调用时,它会设置一个注册表来跟踪调用期间创建的所有对象。在本机调用期间创建的任何对象(即从 JNI 接口函数返回)都将添加到此注册表中。对此类对象的引用称为本地引用。当本地方法返回到 JVM 时,本地方法调用期间创建的所有本地引用都将被销毁。如果您在本地方法调用期间调用回 JVM,则在控制返回到本地方法时,本地引用仍将处于活动状态。如果从本机代码调用的 JVM 再次调用本机代码,则会创建一个新的本地引用注册表,并且应用相同的规则。
(In fact, you can implement you're own JVM executable (i.e. java.exe) using the JNI interface, by creating a JVM (thereby receiving a JNIEnv * pointer), looking up the class given on the command line, and invoking the main() method on it.)
(实际上,您可以使用 JNI 接口实现您自己的 JVM 可执行文件(即 java.exe),方法是创建一个 JVM(从而接收一个 JNIEnv * 指针),查找命令行上给出的类,然后调用main() 方法。)
All references returned from JNI interface methods are local. This means that under normal circumstances you do not need to manually deallocate references return by JNI methods, since they are destroyed when returning to the JVM. Sometimes you still want to destroy them "prematurely", for example when you lots of local references which you want to delete before returning to the JVM.
从 JNI 接口方法返回的所有引用都是本地的。这意味着在正常情况下您不需要手动释放 JNI 方法返回的引用,因为它们在返回 JVM 时会被销毁。有时您仍然想“过早地”销毁它们,例如当您想在返回 JVM 之前删除大量本地引用时。
Global references are created (from local references) by using the NewGlobalRef(). They are added to a special registry and have to be deallocated manually. Global references are only used for Java object which the native code needs to hold a reference to across multiple JNI calls, for example if you have native code triggering events which should be propagated back to Java. In that case, the JNI code needs to store a reference to a Java object which is to receive the event.
全局引用是通过使用 NewGlobalRef() 创建的(从本地引用)。它们被添加到一个特殊的注册表中,并且必须手动解除分配。全局引用仅用于本机代码需要在多个 JNI 调用之间保存引用的 Java 对象,例如,如果您有本机代码触发事件应该传播回 Java。在这种情况下,JNI 代码需要存储对要接收事件的 Java 对象的引用。
Hope this clarifies the memory management issue a little bit.
希望这可以稍微澄清内存管理问题。
回答by Adam Mitz
Re: "A separate, but related question"... you do not need to manually release jclass, jfieldID and jmethodID when you use them in a "local" context. Any actual object references you obtain (not jclass, jfieldID, jmethodID) should be released with DeleteLocalRef.
回复:“一个单独但相关的问题”……在“本地”上下文中使用 jclass、jfieldID 和 jmethodID 时,不需要手动释放它们。您获得的任何实际对象引用(不是 jclass、jfieldID、jmethodID)都应该用 DeleteLocalRef 释放。
回答by ddimitrov
The GC would collect your instance, but it will not automatically release the non-java heap memory allocated in the native code. You should have explicit method in your class to release the c_object instance.
GC 会收集您的实例,但不会自动释放在本机代码中分配的非 Java 堆内存。您应该在类中使用显式方法来释放 c_object 实例。
This is one of the cases where I'd recommend using a finalizer checking if c_object has been released and release it, logging a message if it's not.
这是我建议使用终结器检查 c_object 是否已释放并释放它的情况之一,如果未释放则记录一条消息。
A useful technique is to create a Throwable instance in the Java class constructor and store it in a field (or just initialize the field inline). If the finalizer detects that the class has not been properly disposed it would print the stacktrace, pinpointing the allocation stack.
一个有用的技术是在 Java 类构造函数中创建一个 Throwable 实例并将其存储在一个字段中(或者只是内联初始化该字段)。如果终结器检测到该类没有被正确处理,它将打印堆栈跟踪,精确定位分配堆栈。
A suggestion is to avoid doing straight JNI and go with gluegenor Swig(both generate code and can be statically linked).

