Как зарегистрировать нативный метод в JVM

Java задняя часть JVM NLP
Как зарегистрировать нативный метод в JVM

предисловие

В Java мы часто сталкиваемся с ситуациями, когда нам нужно вызывать собственные методы, и многие классы в базовой библиотеке Java также часто используют собственные методы.При использовании JNI собственные функции должны быть названы в соответствии с согласованным форматом. Не хотите писать длинные функции Имя должно зарегистрировать метод в JVM, вот как зарегистрировать локальный метод в JVM.

соглашение об именовании

Существуют соглашения для имен локальных методов в JVM, которым необходимо следовать при использовании JNI, а именноJava_<fully qualified class name>_method.

Например, здесь написан класс Java для предоставления локального метода шифрования, где метод шифрования является локальным методом, а реализация находится в динамической библиотеке ByteCodeEncryptor, тогда его локальное соответствующее имя функцииJava_com_seaboat_bytecode_ByteCodeEncryptor_encrypt.

package com.seaboat.bytecode;

public class ByteCodeEncryptor {
  static{
    System.loadLibrary("ByteCodeEncryptor"); 
  }
  
  public native static byte[] encrypt(byte[] text);
  
}

registerNatives

Если вы чувствуете, что соглашение об именах локальных функций громоздко, вы можете использовать метод registerNatives для регистрации локальных функций, чтобы вы могли называть функции по своему усмотрению. И считается, что функции, зарегистрированные в JVM через registerNatives, будут более эффективными во время выполнения, потому что поиск функции происходит быстрее.

Как зарегистрироваться

Есть два способа реализовать регистрацию собственного метода:

1. Статические блоки в Java

  • Объявить класс JavaregisterNativesстатический метод.
  • В нативном коде определитеJava_<fully qualified class name>_registerNativesфункция.
  • call перед вызовом других локальных функцийregisterNativesметод.

Например, для класса Object выполните в классе следующее:

private static native void registerNatives();
static {
    registerNatives();
  }

через местныеregisterNativesПривяжите указанный собственный метод к указанной функции, например здесьhashCodeиcloneРодной метод привязан кJVM_IHashCodeиJVM_IHashCodeфункция.

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

2. Используйте JNI_OnLoad

JNI_OnLoadФункция выполняется в JVMSystem.loadLibraryвызывается метод, поэтому его можно вызвать в этом методеRegisterNativesФункция регистрирует локальную функцию. Регистрация нативных методов таким образом избавляет от необходимости объявлять их в классе Java.RegisterNativesлокальный способ регистрации.

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

int JNI_OnLoad(JavaVM* vm, void* reserved)
{
...
if ((*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0])) < 0)
{
    return JNI_ERR;
}
...
}

что делает registerNatives

Структура JNINativeMethod определена для объявления методов и функций следующим образом: имя представляет имя собственного метода Java, сигнатура представляет сигнатуру метода, а fnPtr представляет указатель на функцию.

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

в основном зависит от(*env)->RegisterNativesэта функция,

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

Метод объявлен вJNINativeInterface_В структуре структура содержит все объявления функций интерфейса JNI, а переменные структуры определены в JVM.jni_NativeInterfaceдля использования, только перечисленные здесьRegisterNativesОбъявление функции, остальные функции опущены.

struct JNINativeInterface_ {
    ...
    jint (JNICALL *RegisterNatives) (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
    ...
}

struct JNINativeInterface_ jni_NativeInterface = {
    ...
    jni_RegisterNatives,
    ...
}

заглянутьjni_RegisterNativesЗнайте перед реализацией функцииJNI_ENTRYиJNI_ENDмакрос, эти два макроса извлекли общие части. вJNI_ENDПроще, всего две закрывающие скобки.

#define JNI_ENTRY(result_type, header)  JNI_ENTRY_NO_PRESERVE(result_type, header)    WeakPreserveExceptionMark __wem(thread);

#define JNI_END } }

JNI_ENTRYОсновная логика:

  • Получить объект указателя JavaThread текущего потока выполнения.
  • Создайте объект ThreadInVMfromNative.
  • TRACE_CALL здесь ничего не делает.
  • Создайте объект HandleMarkCleaner.
  • Назначьте поток THREAD в исключениях.
  • Проверьте выравнивание стека.
  • Создайте объект WeakPreserveExceptionMark.
#define JNI_ENTRY_NO_PRESERVE(result_type, header)                   \
extern "C" {                                                         \
  result_type JNICALL header {                                       \
    JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
    assert( !VerifyJNIEnvThread || (thread == Thread::current()), "JNIEnv is only valid in same thread"); \
    ThreadInVMfromNative __tiv(thread);                              \
    debug_only(VMNativeEntryWrapper __vew;)                          \
    VM_ENTRY_BASE(result_type, header, thread)
    
#define VM_ENTRY_BASE(result_type, header, thread)                   \
  TRACE_CALL(result_type, header)                                    \
  HandleMarkCleaner __hm(thread);                                    \
  Thread* THREAD = thread;                                           \
  os::verify_stack_alignment();      

Смотриjni_RegisterNativesКонкретная реализация функции, логика такова:

  • JNIWrapper используется для отладки.
  • И HOTSPOT_JNI_REGISTERNATIVES_ENTRY, и DT_RETURN_MARK используются для dtrace.
  • Создайте объект KlassHandle.
  • Начните обход массива методов, чтобы получить соответствующее имя метода, сигнатуру метода, длину метода и другую информацию.
  • Попробуйте выяснить, существуют ли уже соответствующее имя метода и сигнатура метода в символическом пуле констант, если они не найдены, сгенерируйте исключение, поскольку класс Java был добавлен в пул констант при нормальных обстоятельствах.
  • перечислитьregister_nativeрегистрация функций.
JNI_ENTRY(jint, jni_RegisterNatives(JNIEnv *env, jclass clazz,
                                    const JNINativeMethod *methods,
                                    jint nMethods))
  JNIWrapper("RegisterNatives");
  HOTSPOT_JNI_REGISTERNATIVES_ENTRY(env, clazz, (void *) methods, nMethods);
  jint ret = 0;
  DT_RETURN_MARK(RegisterNatives, jint, (const jint&)ret);

  KlassHandle h_k(thread, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));

  for (int index = 0; index < nMethods; index++) {
    const char* meth_name = methods[index].name;
    const char* meth_sig = methods[index].signature;
    int meth_name_len = (int)strlen(meth_name);

    TempNewSymbol  name = SymbolTable::probe(meth_name, meth_name_len);
    TempNewSymbol  signature = SymbolTable::probe(meth_sig, (int)strlen(meth_sig));

    if (name == NULL || signature == NULL) {
      ResourceMark rm;
      stringStream st;
      st.print("Method %s.%s%s not found", h_k()->external_name(), meth_name, meth_sig);
      THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), -1);
    }

    bool res = register_native(h_k, name, signature,
                               (address) methods[index].fnPtr, THREAD);
    if (!res) {
      ret = -1;
      break;
    }
  }
  return ret;
JNI_END

register_nativeЛогика функции следующая:

  • Найдите указанный метод в соответствующем объекте Klass и сгенерируйте исключение, если он не существует.
  • Если метод не объявлен как нативный, он сначала попытается найти нативный метод с префиксом, потому что префикс некоторых нативных методов может быть установлен в TI-агенте JVM, и если он все еще пуст, возникает исключение будет брошен в конце концов.
  • назовите самое главноеset_native_functionфункция, которая связывает функцию C++ с объектом Method.
  • Если указатель функции равен нулю, вызовитеclear_native_functionОчистите объекты собственного метода.
static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {
  Method* method = k()->lookup_method(name, signature);
  if (method == NULL) {
    ResourceMark rm;
    stringStream st;
    st.print("Method %s name or signature does not match",
             Method::name_and_sig_as_C_string(k(), name, signature));
    THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
  }
  if (!method->is_native()) {
    method = find_prefixed_native(k, name, signature, THREAD);
    if (method == NULL) {
      ResourceMark rm;
      stringStream st;
      st.print("Method %s is not declared as native",
               Method::name_and_sig_as_C_string(k(), name, signature));
      THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
    }
  }

  if (entry != NULL) {
    method->set_native_function(entry,
      Method::native_bind_event_is_interesting);
  } else {
    method->clear_native_function();
  }
  if (PrintJNIResolving) {
    ResourceMark rm(THREAD);
    tty->print_cr("[Registering JNI native method %s.%s]",
      method->method_holder()->external_name(),
      method->name()->as_C_string());
  }
  return true;
}

set_native_functionЛогика функции такова:

  • пройти черезnative_function_addrФункция получает локальный адрес функции, эта функция напрямуюreturn (address*) (this+1);, вы можете видеть, что он напрямую использует адрес объекта метода + 1 в качестве адреса локальной функции. Это возможно, потому что при создании объекта Method будет оцениваться, является ли он собственным методом, и если да, то будут зарезервированы два дополнительных адреса для адреса локальной функции и сигнатуры метода.
  • Определить, равен ли уже локальный адрес функции указателю на функцию, если да, то это означает, что она была привязана и возвращается напрямую, в противном случае продолжается вниз.
  • Событие отправляется, если Jvmti настроен на распространение связанных событий собственного метода.
  • Назначьте указатель функции локальному адресу функции.
  • GCC получает скомпилированный код функции.
void Method::set_native_function(address function, bool post_event_flag) {
  assert(function != NULL, "use clear_native_function to unregister natives");
  assert(!is_method_handle_intrinsic() || function == SharedRuntime::native_method_throw_unsatisfied_link_error_entry(), "");
  address* native_function = native_function_addr();

  address current = *native_function;
  if (current == function) return;
  if (post_event_flag && JvmtiExport::should_post_native_method_bind() &&
      function != NULL) {
    assert(function !=
      SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
      "post_event_flag mis-match");
    JvmtiExport::post_native_method_bind(this, &function);
  }
  *native_function = function;
  CompiledMethod* nm = code(); 
  if (nm != NULL) {
    nm->make_not_entrant();
  }
}

------------- Рекомендуем прочитать ------------

Краткое изложение моих проектов с открытым исходным кодом (машинное и глубокое обучение, НЛП, сетевой ввод-вывод, AIML, протокол mysql, чат-бот)

Зачем писать «Анализ проектирования ядра Tomcat»

Резюме моей статьи за 2017 год — машинное обучение

Краткое изложение моих статей за 2017 год — Java и промежуточное ПО

Резюме моих статей 2017 года — глубокое обучение

Краткое изложение моих статей за 2017 год — исходный код JDK

Резюме моей статьи за 2017 год — обработка естественного языка

Резюме моих статей 2017 года — Java Concurrency


Поговори со мной, задай мне вопросы:

Добро пожаловать, чтобы следовать: