在JNI中,如何根据IBM的性能建议缓存类,methodID和fieldID?

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)

这是使用javahon 生成的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)

  • 关于缓存jclass / jmethodId / jfieldId的最佳答案之一!还有一个问题困扰着我。那么jmethodID / jfieldID呢?它们是不透明的结构,我们如何释放/删除它们,还是应该?还是仅将所有内容(所有已删除的全局引用,jmethodID,jfieID)指向NULL / nullptr就足够了? (2认同)
  • @KonstantinBerkow - 方法 ID 和字段 ID 不由本机代码管理;它们由虚拟机管理并且在卸载关联的类之前一直有效。在虚拟机卸载定义类之前,不能显式删除字段 ID 和方法 ID。只需让 VM 处理它们。如果将它们设置为 NULL 会让您感觉更好,请在释放关联的类后进行,但完全没有必要这样做。 (2认同)

小智 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)