Yup*_*ing 35 java java-native-interface
这是一个与前一篇文章相关的问题,但这篇文章已经解决,现在我想改变问题的方向.
使用JNI时,有必要询问JNIEnv对象jclass以及jmethodID将在C/C++代码中使用的每个类和方法.为了清楚起见,我想从C/C++调用Java构造函数或方法.
由于从Java到C/C++(反之亦然)的通信成本很高,我最初认为最小化这种方法的一种方法是重用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
}
Run Code Online (Sandbox Code Playgroud)
一个更具体(和有用)的例子,我用它来从我的JNI函数中的任何地方抛出异常:
jclass jniExceptionClass = NULL;
void throwJavaException(JNIEnv *env, const char* msg) {
if (!jniExceptionClass) {
jniExceptionClass = env->FindClass("example/JNIRuntimeException");
}
if (jniExceptionClass)
env->ThrowNew(jniExceptionClass, msg);
}
}
Run Code Online (Sandbox Code Playgroud)
问题是我继续使用这种模式并得到了一个只能通过不重用这个变量来解决的分段错误(这是前一篇文章的解决方案).
问题是:
jclass和jmethodID通过不同的JNI函数是非法的?我认为这个价值总是一样的.jclass和jmethodID每个JNI函数的影响/开销是多少?bma*_*ies 58
这里的规则很清楚.方法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);
Run Code Online (Sandbox Code Playgroud)
检查宏调用:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
在内部JNI_OnLoad,您需要在缓存之前使用返回NewGlobalRef的jclass值FindClass.
然后,在里面JNI_OnUnload你打电话DeleteGlobalRef给他们.
正如其他人已经写道
jmethodID毫无问题地存储在静态C ++变量中jobject或jclass静态C ++变量转换为全局对象后,可以将它们存储在本地env->NewGloablRef()我只想在此处添加其他信息:将jclass存储在静态C ++变量中的主要原因是您认为env->FindClass()每次调用都是性能问题。
但是,我测量的速度的FindClass()用的性能计数器QueryPerformanceCounter()在Windows API。结果令人惊讶:
在具有3,6 GHz CPU的计算机上,执行
jcass p_Container = env->FindClass("java/awt/Container");
Run Code Online (Sandbox Code Playgroud)
耗时介于0.01毫秒至0.02毫秒之间。那太快了。我查看了Java源代码,他们使用了存储类的Dictionary。这似乎非常有效地实现了。
我测试了更多的类,结果如下:
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
Run Code Online (Sandbox Code Playgroud)
上面的值来自Java事件分配器线程。如果我在CreateThread()由我创建的本机Windows线程中执行相同的代码,则其运行速度甚至提高了10倍。为什么?
因此,如果您不FindClass()经常调用,则在调用JNI函数而不是创建全局引用并将其存储在静态变量中时,按需调用绝对没有问题。
另一个重要主题是线程安全。在Java中,每个线程都有其自己的独立JNIEnv结构。
jobject或jclass在任何Java线程中均有效。JNIEnv调用线程的一个函数调用中有效,并且在JNI代码返回Java时被垃圾回收。现在,这取决于您使用的线程:如果您向其注册C ++函数env->RegisterNatives()并且Java代码正在调用JNI函数,则必须存储所有要稍后使用的对象,作为全局对象,否则它们将被垃圾回收。
但是,如果您使用CraeteThread()API(在Windows上)创建自己的线程并JNIEnv通过调用获得结构,AttachCurrentThreadAsDaemon()则完全适用其他规则:由于它是您自己的线程,永远不会将控制权返回给Java,因此垃圾收集器将永远不会清理那些您已经在线程上创建了,甚至可以将本地对象存储在静态C ++变量中而不会出现问题(但是无法从其他线程访问这些本地对象)。在这种情况下,手动清除所有本地实例非常重要,env->DeleteLocalRef()否则会发生内存泄漏。(但是,当您调用时,垃圾收集器可能会释放所有本地对象DetachCurrentThread(),我从未测试过。)
我强烈建议将所有本地对象加载到调用DeleteLocalRef()其析构函数的包装器类中。这是避免内存泄漏的防弹方法。
开发JNI代码可能非常麻烦,并且可能会导致您不了解的崩溃。要查找崩溃原因,请打开DOS窗口,并使用以下命令启动Java应用程序:
java -Xcheck:jni -jar MyApplication.jar
Run Code Online (Sandbox Code Playgroud)
然后您将看到JNI代码中发生了什么问题。例如:
FATAL ERROR in native method: Bad global or local ref passed to JNI
Run Code Online (Sandbox Code Playgroud)
您将在Java创建的日志文件中的JAR文件所在的同一文件夹中找到stacktrace:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
20015 次 |
| 最近记录: |