Java 为什么我不应该在JNI中重用jclass和/或jmethodID?
这是一个与相关的问题,但这篇文章已经解决了,现在我想改变问题的方向 在使用JNI时,有必要询问Java 为什么我不应该在JNI中重用jclass和/或jmethodID?,java,java-native-interface,Java,Java Native Interface,这是一个与相关的问题,但这篇文章已经解决了,现在我想改变问题的方向 在使用JNI时,有必要询问jclass的JNIEnv对象,以及将在C/C++代码中使用的每个类和方法的jmethodID。为了清楚起见,我想从C/C++调用Java构造函数或方法 由于从Java到C/C++(和viceversa)的通信成本很高,我最初认为将其最小化的一种方法是重用jclass和jmethodID。因此,我将此实例保存在全局变量中,如下所示: jclass someClass = NULL; jmetho
jclass
的JNIEnv
对象,以及将在C/C++代码中使用的每个类和方法的jmethodID
。为了清楚起见,我想从C/C++调用Java构造函数或方法
由于从Java到C/C++(和viceversa)的通信成本很高,我最初认为将其最小化的一种方法是重用jclass
和jmethodID
。因此,我将此实例保存在全局变量中,如下所示:
jclass someClass = NULL;
jmethodID someMethod = NULL;
JNIEXPORT jobject JNICALL Java_example_method1(JNIEnv *env, jobject jobj) {
// initialize someClass and someMethod if they are NULL
// use someClass and someMethod to call java (for example, thru NewObject)
}
JNIEXPORT jobject JNICALL Java_example_method2(JNIEnv *env, jobject jobj) {
// initialize someClass and someMethod if they are NULL
// use someClass and someMethod to call java again
}
一个更具体(且有用)的示例,我使用它从JNI函数中的任何位置抛出异常:
jclass jniExceptionClass = NULL;
void throwJavaException(JNIEnv *env, const char* msg) {
if (!jniExceptionClass) {
jniExceptionClass = env->FindClass("example/JNIRuntimeException");
}
if (jniExceptionClass)
env->ThrowNew(jniExceptionClass, msg);
}
}
问题是,我继续使用这个模式,并得到了一个分段错误,只有通过不重用这个变量才能解决(这是前一篇文章的解决方案)
问题是:
- 为什么通过不同的JNI函数重用
和jclass
是非法的?我认为这些价值观总是一样的jmethodID
- 出于好奇:为每个JNI函数初始化所有必要的
和jclass
有什么影响/开销jmethodID
抱歉,我不知道性能方面的问题,任何时候我使用JNI与手头的任务相比都是微不足道的。这里的规则很清楚。方法ID和字段ID值是永久的。你可以抓住它们。查找需要一些时间 另一方面,
jclass
,通常是本地引用。本地引用最多只能在对JNI函数的单个调用持续时间内生存
如果需要优化,必须让JVM为您创建一个全局引用。获取和保留对诸如java.lang.String
之类的公共类的引用并不少见
当然,持有这样一个对类的引用将防止它(类)被垃圾收集
jclass local = env->FindClass(CLS_JAVA_LANG_STRING);
_CHECK_JAVA_EXCEPTION(env);
java_lang_string_class = (jclass)env->NewGlobalRef(local);
_CHECK_JAVA_EXCEPTION(env);
env->DeleteLocalRef(local);
_CHECK_JAVA_EXCEPTION(env);
check宏调用:
static inline void
check_java_exception(JNIEnv *env, int line)
{
UNUSED(line);
if(env->ExceptionOccurred()) {
#ifdef DEBUG
fprintf(stderr, "Java exception at rlpjni.cpp line %d\n", line);
env->ExceptionDescribe();
abort();
#endif
throw bt_rlpjni_java_is_upset();
}
}
在
JNI_OnLoad
内部,在缓存FindClass
返回的jclass
值之前,需要使用NewGlobalRef
然后,在
JNI\u onload
中,您可以对它们调用DeleteGlobalRef
。您可以缓存并使用您的方法/函数/类ID,如果您为其编写了适当的代码,则可以安全地使用它们。我已经回答并发布了关于如何遵循IBM缓存性能建议的明确代码。正如其他人所写
< L>可以在一个静态C++变量中存储<代码> JMeodod> /Cube,没有问题< /LI>
<> LI>通过调用<代码> EnV--NexGloBrdFor(< /Calp> < /LI>),将C++中的本地代码<代码> JOBJET< <代码>或<代码> JCys<代码>转换为全局对象。
我只想在此添加一个附加信息:
将JC类存储在静态C++变量中的主要原因是,您认为每次调用“代码> EnV--FunCype())/>代码是一个性能问题。
但是我在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事件调度程序线程。如果我在本机Windows线程中执行与我创建的CreateThread()
相同的代码,那么它的运行速度将提高10倍。为什么?
因此,如果不经常调用FindClass()
,那么在调用JNI函数时按需调用它,而不是创建全局引用并将其存储在静态变量中,绝对没有问题
另一个重要主题是线程安全。在Java中,每个线程都有自己独立的
JNIEnv
结构
jobject
或jclass
在任何Java线程中都有效李>
JNIEnv
中的一个函数调用中有效,并且在JNI代码返回Java时被垃圾收集现在取决于你使用的线程:如果你登记C++函数,使用<代码> Env> > RealStnjava()/代码>,Java代码正在调用你的JNI函数,那么你必须把所有的对象,你想以后使用,作为全局对象,否则它们会被垃圾收集。 但是,如果您使用
CraeteThread()
API(在Windows上)创建自己的线程,并通过调用AttachCurrentThreadAsDaemon()
来获得JNIEnv
结构,那么其他规则将完全适用:因为它是您自己的线程,永远不会将控制权返回给Java,垃圾收集器永远不会清理您在线程上创建的对象,甚至可以在静态C++变量中存储本地对象而不存在问题(但是这些线程不能从其他线程访问)。在这种情况下,使用env->DeleteLocalRef()。(但是,当您调用我从未测试过的DetachCurrentThread()
时,垃圾收集器可能会释放所有本地对象。)
我强烈建议将所有本地对象加载到在其析构函数中调用DeleteLocalRef()
的包装类中。这是一种避免内存泄漏的防弹方法
开发jnic
java -Xcheck:jni -jar MyApplication.jar
FATAL ERROR in native method: Bad global or local ref passed to JNI
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...