Suh*_*pta 21 java performance java-native-interface caching
我在IBM上读到了这一点
要访问Java对象的字段并调用其方法,本机代码必须调用FindClass(),GetFieldID(),GetMethodId()和GetStaticMethodID().对于GetFieldID(),GetMethodID()和GetStaticMethodID(),为给定类返回的ID在JVM进程的生命周期内不会更改.但是获取字段或方法的调用可能需要在JVM中进行大量工作,因为字段和方法可能已经从超类继承,使得JVM在类层次结构中向上移动以找到它们.因为给定类的ID是相同的,所以您应该查找它们一次,然后重复使用它们.同样,查找类对象可能很昂贵,因此它们也应该被缓存.
一个人如何缓存的methodID
,fieldID
以及class
在JNI对象?是否有必须遵循的内置方法或特定程序?
Jos*_*hDM 44
没有内置的方法可供遵循,但是这里有一个标准的,干净的,可重复的实现,显示了我如何实践IBM的建议.
我将假设您从Java调用DLL,并且在整个应用程序生命周期中多次引用它.
命名示例Native Java Class org.stackoverflow.jni.NativeClazz
,它将实现2个内置的JNI方法JNI_OnLoad()
和JNI_OnUnload()
.
void JNI_OnLoad(JavaVM*vm,void*reserved): 此方法将用于将类ID注册为全局变量,并将方法ID和字段ID分配给静态变量.当Java VM加载驱动程序时,将自动调用该方法; 它只在驾驶员生命周期中被调用一次.
void JNI_OnUnload(JavaVM*vm,void*reserved): 此方法将用于释放注册的任何全局变量JNI_OnLoad()
.VM将JNI_OnUnload()
在应用程序关闭之前立即自动调用.
基本原理:我的理解是必须将类ID注册为全局引用,以维护任何关联的方法ID /字段ID的可行性.如果未执行此操作并且从JVM卸载类,则在类重新加载时,方法ID /字段ID可能不同.如果将类ID注册为全局引用,则不需要将关联的方法ID和字段ID注册为全局引用.将类ID注册为全局引用可防止关联的Java类卸载,从而稳定方法ID /字段ID值.应删除全局引用,包括类ID JNI_OnUnload()
.
方法ID和字段ID不由本机代码管理; 它们由虚拟机管理,并且在卸载关联类之前一直有效.在虚拟机卸载定义类之前,无法显式删除字段ID和方法ID; 它们可以留给VM卸载后处理.
示例代码
以下C++代码部分中的注释解释了全局注册变量.
这是BeanObject
表示数据对象的Java类:
package org.stackoverflow.data;
public class BeanObject {
String foo = "";
public String getFoo() {
return foo;
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个骨架Java类NativeClazz
:
package org.stackoverflow.jni;
import org.stackoverflow.data.BeanObject;
public class NativeClazz {
// Static area for forced initialization
static {
// Load Native Library (C++); calls JNI_OnLoad()
System.loadLibrary("Native_Library_File_Name");
}
/**
* A static native method you plan to call.
*/
public static native void staticNativeMethod(BeanObject bean);
/**
* A non-static native method you plan to call, to show this also works with
* instantiated Java classes.
*/
public native void instanceNativeMethod(BeanObject bean);
}
Run Code Online (Sandbox Code Playgroud)
这是使用javah
on 生成的C++头文件NativeClazz
:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_stackoverflow_jni_NativeClazz */
#ifndef _Included_org_stackoverflow_jni_NativeClazz
#define _Included_org_stackoverflow_jni_NativeClazz
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_stackoverflow_jni_NativeClazz_staticNativeMethod
* Method: staticNativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod
(JNIEnv *, jclass, jobject);
/*
* Class: org_stackoverflow_jni_NativeClazz_instanceNativeMethod
* Method: instanceNativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod
(JNIEnv *, jobject, jobject);
#ifdef __cplusplus
}
#endif
#endif
Run Code Online (Sandbox Code Playgroud)
这是实现头文件的C++ .cpp文件:
#include "org_stackoverflow_jni_NativeClazz.h"
using namespace std;
/**************************************************************
* Static Global Variables to cache Java Class and Method IDs
**************************************************************/
static jclass JC_BeanObject;
static jmethodID JMID_BeanObject_getFoo;
/**************************************************************
* Declare JNI_VERSION for use in JNI_Onload/JNI_OnUnLoad
* Change value if a Java upgrade requires it (prior: JNI_VERSION_1_6)
**************************************************************/
static jint JNI_VERSION = JNI_VERSION_1_8;
/**************************************************************
* Initialize the static Class and Method Id variables
**************************************************************/
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// Obtain the JNIEnv from the VM and confirm JNI_VERSION
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) {
return JNI_ERR;
}
// Temporary local reference holder
jclass tempLocalClassRef;
// STEP 1/3 : Load the class id
tempLocalClassRef = env->FindClass("org/stackoverflow/data/BeanObject");
// STEP 2/3 : Assign the ClassId as a Global Reference
JC_BeanObject = (jclass) env->NewGlobalRef(tempLocalClassRef);
// STEP 3/3 : Delete the no longer needed local reference
env->DeleteLocalRef(tempLocalClassRef);
// Load the method id
JMID_BeanObject_getFoo = env->GetMethodID(JC_BeanObject, "getFoo", "(Ljava/lang/String;)V");
// ... repeat prior line for any other methods of BeanObject
// ... repeat STEPS 1-3 for any other classes; re-use tempLocalClassRef.
// Return the JNI Version as required by method
return JNI_VERSION;
}
/**************************************************************
* Destroy the global static Class Id variables
**************************************************************/
void JNI_OnUnload(JavaVM *vm, void *reserved) {
// Obtain the JNIEnv from the VM
// NOTE: some re-do the JNI Version check here, but I find that redundant
JNIEnv* env;
vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION);
// Destroy the global references
env->DeleteGlobalRef(JC_BeanObject);
// ... repeat for any other global references
}
/**************************************************************
* A Static Native Method
**************************************************************/
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod
(JNIEnv * env, jclass clazz, jobject jBeanObject) {
// Retrieve jstring from the Java Object
jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo);
// Make accessible to C++
const char * cFoo = env->GetStringUTFChars(jFoo, NULL);
// Do something with cFoo...
// Release Resources
env->ReleaseStringUTFChars(jFoo, cFoo);
env->DeleteLocalRef(jFoo);
}
/**************************************************************
* Instance / Non-Static Native Method
**************************************************************/
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod
(JNIEnv * env, jobject selfReference, jobject jBeanObject) {
// Retrieve jstring from the Java Object
jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo);
// Make accessible to C++
const char * cFoo = env->GetStringUTFChars(jFoo, NULL);
// Do something with cFoo...
// Release Resources
env->ReleaseStringUTFChars(jFoo, cFoo);
env->DeleteLocalRef(jFoo);
}
Run Code Online (Sandbox Code Playgroud)
小智 8
以下是我如何实践IBM的建议.考虑像这样的demo java类:
public class SimpleClazz {
public int value = 10;
public native int getValue();
static {
// Load Native Library
System.loadLibrary("The native library name");
}
}
Run Code Online (Sandbox Code Playgroud)
相应的jni头文件是这样的:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SimpleClazz */
#ifndef _Included_SimpleClazz
#define _Included_SimpleClazz
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: SimpleClazz
* Method: getValue
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_SimpleClazz_getValue
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Run Code Online (Sandbox Code Playgroud)
根据IBM的建议,我们需要缓存已使用的类SimpleClazz
和对象成员的字段id value
.
在学好这篇好文章之后,我将缓存SimpleClazz
函数JNI_OnLoad
,该函数在加载本机库时调用(例如,通过System.loadLibrary).在JNI_Onload
,我们找到类并将此jclass存储为全局字段.
此外,在本机实现中getValue
,我们使用静态局部变量来缓存字段id value
.此设计旨在确保此字段ID可以在更好的范围内,而不是在全局范围内.这种设计的缺点是每次调用此函数时我们都需要与NULL进行比较.我从"Java Native Interface:Programmer's Guide and Specification "一书的第4.4.1节中学到了这个设计.
最后,我们还需要编写函数JNI_OnUnload
,当包含本机库的类加载器被垃圾收集时调用该函数.在这个函数中,我们发布了jclass的全局引用.
我的cpp实现如下所示:
#include <jni.h>
#include <SimpleClazz.h>
static jclass simpleCls;
// According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnLoad
// The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary).
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
} else {
jclass localSimpleCls = (*env)->FindClass("SimpleClazz");
if (localSimpleCls == NULL) {
return JNI_ERR;
}
simpleCls = (jclass) (*env)->NewGlobalRef(env, localSimpleCls);
}
return JNI_VERSION_1_6;
}
JNIEXPORT jint JNICALL Java_SimpleClazz_getValue(JNIEnv * env, jobject thiz){
static jfieldID valueID = NULL;
if (valueID == NULL) {
valueID = (*env)->GetFieldID(env, simpleCls, "value", "I");
if (valueID == NULL){
return JNI_ERR; // Exception thrown
}
}
jint value = (*env)->GetIntField(env, thiz, valueID);
return value;
}
// According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnUnload
// The VM calls JNI_OnUnload when the class loader containing the native library is garbage collected.
void JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
// Something is wrong but nothing we can do about this :(
return;
} else {
if (0 != NULL){
(*env)->DeleteGlobalRef(env, simpleCls);
}
}
}
Run Code Online (Sandbox Code Playgroud)