正如其他人已经写的那样
- 您可以毫无问题地将
jmethodID 存储在静态 C++ 变量中
- 通过调用
env->NewGloablRef() 将它们转换为全局对象后,您可以将本地jobject 或jclass 存储在静态C++ 变量中
我只想在此处添加其他信息:
将 jclass 存储在静态 C++ 变量中的主要原因是您认为每次调用 env->FindClass() 是一个性能问题。
但我使用 Windows 上的 QueryPerformanceCounter() API 的性能计数器测量了 FindClass() 的速度。结果令人惊讶:
在我的具有 3,6 GHz CPU 的计算机上执行
jcass p_Container = env->FindClass("java/awt/Container");
需要 0.01 毫秒到 0.02 毫秒。这是令人难以置信的快。我查看了 Java 源代码,他们使用存储类的字典。这似乎非常有效地实现。
我又测试了一些类,结果如下:
Elapsed 0.002061 ms for java/net/URL
Elapsed 0.044390 ms for java/lang/Boolean
Elapsed 0.019235 ms for java/lang/Character
Elapsed 0.018372 ms for java/lang/Number
Elapsed 0.017931 ms for java/lang/Byte
Elapsed 0.017589 ms for java/lang/Short
Elapsed 0.017371 ms for java/lang/Integer
Elapsed 0.015637 ms for java/lang/Double
Elapsed 0.018173 ms for java/lang/String
Elapsed 0.015895 ms for java/math/BigDecimal
Elapsed 0.016204 ms for java/awt/Rectangle
Elapsed 0.016272 ms for java/awt/Point
Elapsed 0.001817 ms for java/lang/Object
Elapsed 0.016057 ms for java/lang/Class
Elapsed 0.016829 ms for java/net/URLClassLoader
Elapsed 0.017807 ms for java/lang/reflect/Field
Elapsed 0.016658 ms for java/util/Locale
Elapsed 0.015720 ms for java/lang/System
Elapsed 0.014669 ms for javax/swing/JTable
Elapsed 0.017276 ms for javax/swing/JComboBox
Elapsed 0.014777 ms for javax/swing/JList
Elapsed 0.015597 ms for java/awt/Component
Elapsed 0.015223 ms for javax/swing/JComponent
Elapsed 0.017385 ms for java/lang/Throwable
Elapsed 0.015089 ms for java/lang/StackTraceElement
以上值来自 Java 事件调度线程。如果我在 CreateThread() 创建的本地 Windows 线程中执行相同的代码,它的运行速度甚至会快 10 倍。为什么?
因此,如果您不经常调用 FindClass(),那么在调用 JNI 函数时按需调用它绝对没有问题,而不是创建全局引用并将其存储在静态变量中。
另一个重要的话题是线程安全。在 Java 中,每个线程都有自己独立的 JNIEnv 结构。
- 全局
jobject 或jclass 在任何Java 线程中都有效。
- 本地对象仅在调用线程的
JNIEnv 中的一个函数调用中有效,并在 JNI 代码返回 Java 时进行垃圾回收。
现在这取决于您使用的线程:如果您使用 env->RegisterNatives() 注册您的 C++ 函数并且 Java 代码正在调用您的 JNI 函数,那么您必须将您以后想要使用的所有对象存储为全局对象否则他们会被垃圾回收。
但是如果您使用CraeteThread() API(在Windows 上)创建自己的线程并通过调用AttachCurrentThreadAsDaemon() 获得JNIEnv 结构,那么将完全适用其他规则:因为它是您自己的线程,所以永远不会返回控制权对于 Java,垃圾收集器永远不会清理您在线程上创建的对象,您甚至可以毫无问题地将本地对象存储在静态 C++ 变量中(但不能从其他线程访问这些对象)。在这种情况下,使用env->DeleteLocalRef() 手动清理所有本地实例非常重要,否则会出现内存泄漏。 (但是,当您调用 DetachCurrentThread() 时,垃圾收集器可能会释放所有本地对象,我从未测试过。)
我强烈建议将所有本地对象加载到一个包装类中,该类在其析构函数中调用DeleteLocalRef()。这是避免内存泄漏的万无一失的方法。
开发 JNI 代码可能非常繁琐,并且您可能会遇到不理解的崩溃。要查找崩溃的原因,请打开 DOS 窗口并使用以下命令启动 Java 应用程序:
java -Xcheck:jni -jar MyApplication.jar
然后你会看到JNI代码中发生了什么问题。例如:
FATAL ERROR in native method: Bad global or local ref passed to JNI
您将在 Java 创建的日志文件中找到堆栈跟踪,该文件位于您拥有 JAR 文件的同一文件夹中:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...