博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HotSpotVM JNI实现浅析
阅读量:7103 次
发布时间:2019-06-28

本文共 21309 字,大约阅读时间需要 71 分钟。

最近想看下HotSpotVM是怎么找到一个native方法的实现的,例如和,最后发现是两种不同的方式。

nativeLookup

通过可以知道,JVM通过加载,将native方法链接过去,具体native方法解析成哪个符号,是按下面的约定来的,

Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:

  • the prefix Java_
  • a mangled fully-qualified class name
  • an underscore (“_”) separator
  • a mangled method name
  • for overloaded native methods, two underscores (“__”) followed by the mangled argument signature

HotspotVM对这部分的实现在中,

address NativeLookup::lookup(methodHandle method, bool& in_base_library, TRAPS) {  // 调用has_native_function  if (!method->has_native_function()) {    // lookup_base    address entry = lookup_base(method, in_base_library, CHECK_NULL);    // 调用set_native_function    method->set_native_function(entry,      methodOopDesc::native_bind_event_is_interesting);    // -verbose:jni printing    if (PrintJNIResolving) {      ResourceMark rm(THREAD);      tty->print_cr("[Dynamic-linking native method %s.%s ... JNI]",        Klass::cast(method->method_holder())->external_name(),        method->name()->as_C_string());    }  }  return method->native_function();}
address NativeLookup::lookup_base(methodHandle method, bool& in_base_library, TRAPS) {  address entry = NULL;  ResourceMark rm(THREAD);  entry = lookup_entry(method, in_base_library, THREAD);  if (entry != NULL) return entry;  // standard native method resolution has failed.  Check if there are any  // JVM TI prefixes which have been applied to the native method name.  entry = lookup_entry_prefixed(method, in_base_library, THREAD);  if (entry != NULL) return entry;  // Native function not found, throw UnsatisfiedLinkError  THROW_MSG_0(vmSymbols::java_lang_UnsatisfiedLinkError(),              method->name_and_sig_as_C_string());}
// Check all the formats of native implementation name to see if there is one// for the specified method.address NativeLookup::lookup_entry(methodHandle method, bool& in_base_library, TRAPS) {  address entry = NULL;  in_base_library = false;  // Compute pure name  char* pure_name = pure_jni_name(method);  // Compute argument size  int args_size = 1                             // JNIEnv                + (method->is_static() ? 1 : 0) // class for static methods                + method->size_of_parameters(); // actual parameters  // 1) Try JNI short style  entry = lookup_style(method, pure_name, "",        args_size, true,  in_base_library, CHECK_NULL);  if (entry != NULL) return entry;  // Compute long name  char* long_name = long_jni_name(method);  // 2) Try JNI long style  entry = lookup_style(method, pure_name, long_name, args_size, true,  in_base_library, CHECK_NULL);  if (entry != NULL) return entry;  // 3) Try JNI short style without os prefix/suffix  entry = lookup_style(method, pure_name, "",        args_size, false, in_base_library, CHECK_NULL);  if (entry != NULL) return entry;  // 4) Try JNI long style without os prefix/suffix  entry = lookup_style(method, pure_name, long_name, args_size, false, in_base_library, CHECK_NULL);  return entry; // NULL indicates not found}
char* NativeLookup::pure_jni_name(methodHandle method) {  stringStream st;  // Prefix  st.print("Java_");  // Klass name  mangle_name_on(&st, method->klass_name());  st.print("_");  // Method name  mangle_name_on(&st, method->name());  return st.as_string();}char* NativeLookup::long_jni_name(methodHandle method) {  // Signature ignore the wrapping parenteses and the trailing return type  stringStream st;  Symbol* signature = method->signature();  st.print("__");  // find ')'  int end;  for (end = 0; end < signature->utf8_length() && signature->byte_at(end) != ')'; end++);  // skip first '('  mangle_name_on(&st, signature, 1, end);  return st.as_string();}
address NativeLookup::lookup_style(methodHandle method, char* pure_name, const char* long_name, int args_size, bool os_style, bool& in_base_library, TRAPS) {  address entry;  // Compute complete JNI name for style  stringStream st;  if (os_style) os::print_jni_name_prefix_on(&st, args_size);  st.print_raw(pure_name);  st.print_raw(long_name);  if (os_style) os::print_jni_name_suffix_on(&st, args_size);  char* jni_name = st.as_string();  // If the loader is null we have a system class, so we attempt a lookup in  // the native Java library. This takes care of any bootstrapping problems.  // Note: It is critical for bootstrapping that Java_java_lang_ClassLoader_00024NativeLibrary_find  // gets found the first time around - otherwise an infinite loop can occure. This is  // another VM/library dependency  Handle loader(THREAD,                instanceKlass::cast(method->method_holder())->class_loader());  if (loader.is_null()) {    entry = lookup_special_native(jni_name);    if (entry == NULL) {       // 查找本地动态链接库       entry = (address) os::dll_lookup(os::native_java_library(), jni_name);    }    if (entry != NULL) {      in_base_library = true;      return entry;    }  }  // Otherwise call static method findNative in ClassLoader  KlassHandle   klass (THREAD, SystemDictionary::ClassLoader_klass());  Handle name_arg = java_lang_String::create_from_str(jni_name, CHECK_NULL);  JavaValue result(T_LONG);  JavaCalls::call_static(&result,                         klass,                         vmSymbols::findNative_name(),                         vmSymbols::classloader_string_long_signature(),                         // Arguments                         loader,                         name_arg,                         CHECK_NULL);  entry = (address) (intptr_t) result.get_jlong();  if (entry == NULL) {    // findNative didn't find it, if there are any agent libraries look in them    AgentLibrary* agent;    for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {      entry = (address) os::dll_lookup(agent->os_lib(), jni_name);      if (entry != NULL) {        return entry;      }    }  }  return entry;}
最后便是找到本地动态链接库,调用
查找符号表。先看查找本地动态链接库的逻辑,
static void* _native_java_library = NULL;void* os::native_java_library() {  if (_native_java_library == NULL) {    char buffer[JVM_MAXPATHLEN];    char ebuf[1024];    // Try to load verify dll first. In 1.3 java dll depends on it and is not    // always able to find it when the loading executable is outside the JDK.    // In order to keep working with 1.2 we ignore any loading errors.    dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "verify");    dll_load(buffer, ebuf, sizeof(ebuf));    // Load java dll    dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "java");    _native_java_library = dll_load(buffer, ebuf, sizeof(ebuf));    if (_native_java_library == NULL) {      vm_exit_during_initialization("Unable to load native library", ebuf);    }#if defined(__OpenBSD__)    // Work-around OpenBSD's lack of $ORIGIN support by pre-loading libnet.so    // ignore errors    dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "net");    dll_load(buffer, ebuf, sizeof(ebuf));#endif  }  static jboolean onLoaded = JNI_FALSE;  if (onLoaded) {    // We may have to wait to fire OnLoad until TLS is initialized.    if (ThreadLocalStorage::is_initialized()) {      // The JNI_OnLoad handling is normally done by method load in      // java.lang.ClassLoader$NativeLibrary, but the VM loads the base library      // explicitly so we have to check for JNI_OnLoad as well      const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;      JNI_OnLoad_t JNI_OnLoad = CAST_TO_FN_PTR(          JNI_OnLoad_t, dll_lookup(_native_java_library, onLoadSymbols[0]));      if (JNI_OnLoad != NULL) {        JavaThread* thread = JavaThread::current();        ThreadToNativeFromVM ttn(thread);        HandleMark hm(thread);        jint ver = (*JNI_OnLoad)(&main_vm, NULL);        onLoaded = JNI_TRUE;        if (!Threads::is_supported_jni_version_including_1_1(ver)) {          vm_exit_during_initialization("Unsupported JNI version");        }      }    }  }  return _native_java_library;}
dll_build_name
的时候,因为各个平台的动态链接库文件格式不同,例如上面加载的java动态链接库,在Win下是
java.dll
文件,而Linux下则是
libjava.so
,Linux平台下的这个逻辑在
中实现,
void os::dll_build_name(char* buffer, size_t buflen,                        const char* pname, const char* fname) {  // Copied from libhpi  const size_t pnamelen = pname ? strlen(pname) : 0;  // Quietly truncate on buffer overflow.  Should be an error.  if (pnamelen + strlen(fname) + 10 > (size_t) buflen) {      *buffer = '\0';      return;  }  if (pnamelen == 0) {    snprintf(buffer, buflen, "lib%s.so", fname);  } else if (strchr(pname, *os::path_separator()) != NULL) {    int n;    char** pelements = split_path(pname, &n);    for (int i = 0 ; i < n ; i++) {      // Really shouldn't be NULL, but check can't hurt      if (pelements[i] == NULL || strlen(pelements[i]) == 0) {        continue; // skip the empty path values      }      snprintf(buffer, buflen, "%s/lib%s.so", pelements[i], fname);      if (file_exists(buffer)) {        break;      }    }    // release the storage    for (int i = 0 ; i < n ; i++) {      if (pelements[i] != NULL) {        FREE_C_HEAP_ARRAY(char, pelements[i], mtInternal);      }    }    if (pelements != NULL) {      FREE_C_HEAP_ARRAY(char*, pelements, mtInternal);    }  } else {    snprintf(buffer, buflen, "%s/lib%s.so", pname, fname);  }}
动态链接库文件格式不同,
dll_lookup
查找符号表的实现肯定也不同,Linux下的实现还是在os_linux.cpp中,
/* * glibc-2.0 libdl is not MT safe.  If you are building with any glibc, * chances are you might want to run the generated bits against glibc-2.0 * libdl.so, so always use locking for any version of glibc. */void* os::dll_lookup(void* handle, const char* name) {  pthread_mutex_lock(&dl_mutex);  void* res = dlsym(handle, name);  pthread_mutex_unlock(&dl_mutex);  return res;}

dlsym是Linux的系统调用,参考。

Thread.start0

在有这么几行代码,

/* Make sure registerNatives is the first thing 
does. */ private static native void registerNatives(); static { registerNatives(); }
根据上面的规则,在libjava.so中应该存在
Java_java_lang_Thread_registerNatives
这样的方法,那么这个方法在哪?不是在OpenJDK的hotspot子项目下,而是在jdk子项目下,
static JNINativeMethod methods[] = {    {"start0",           "()V",        (void *)&JVM_StartThread},    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},    {"resume0",          "()V",        (void *)&JVM_ResumeThread},    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},    {"yield",            "()V",        (void *)&JVM_Yield},    {"sleep",            "(J)V",       (void *)&JVM_Sleep},    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},};JNIEXPORT void JNICALLJava_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls){    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));}
看代码发现,并没有一个
Java_java_lang_Thread_start0
这样的方法,但是在
Java_java_lang_Thread_registerNatives
中调用了
RegisterNatives
,这货干嘛用的?先看它的定义,在
中,是
JNINativeInterface_
中的一个函数指针(C中都是通过这种方式实现接口抽象的),
struct JNINativeInterface_ {    ...    jint (JNICALL *RegisterNatives)      (JNIEnv *env, jclass clazz, const JNINativeMethod *methods,       jint nMethods);    ...}
具体实现在
中,
struct JNINativeInterface_ jni_NativeInterface = {    ...    jni_RegisterNatives,    ...}
JNI_ENTRY(jint, jni_RegisterNatives(JNIEnv *env, jclass clazz,                                    const JNINativeMethod *methods,                                    jint nMethods))  JNIWrapper("RegisterNatives");#ifndef USDT2  DTRACE_PROBE4(hotspot_jni, RegisterNatives__entry, env, clazz, methods, nMethods);#else /* USDT2 */  HOTSPOT_JNI_REGISTERNATIVES_ENTRY(                                    env, clazz, (void *) methods, nMethods);#endif /* USDT2 */  jint ret = 0;  DT_RETURN_MARK(RegisterNatives, jint, (const jint&)ret);  KlassHandle h_k(thread, java_lang_Class::as_klassOop(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);    // The class should have been loaded (we have an instance of the class    // passed in) so the method and signature should already be in the symbol    // table.  If they're not there, the method doesn't exist.    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", Klass::cast(h_k())->external_name(), meth_name, meth_sig);      // Must return negative value on failure      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
static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {  methodOop method = Klass::cast(k())->lookup_method(name, signature);  if (method == NULL) {    ResourceMark rm;    stringStream st;    st.print("Method %s name or signature does not match",             methodOopDesc::name_and_sig_as_C_string(Klass::cast(k()), name, signature));    THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);  }  if (!method->is_native()) {    // trying to register to a non-native method, see if a JVM TI agent has added prefix(es)    method = find_prefixed_native(k, name, signature, THREAD);    if (method == NULL) {      ResourceMark rm;      stringStream st;      st.print("Method %s is not declared as native",               methodOopDesc::name_and_sig_as_C_string(Klass::cast(k()), name, signature));      THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);    }  }  if (entry != NULL) {    // 调用set_native_function    method->set_native_function(entry,      methodOopDesc::native_bind_event_is_interesting);  } else {    method->clear_native_function();  }  if (PrintJNIResolving) {    ResourceMark rm(THREAD);    tty->print_cr("[Registering JNI native method %s.%s]",      Klass::cast(method->method_holder())->external_name(),      method->name()->as_C_string());  }  return true;}
有没有发现最后调用
set_native_function
似曾相识,是的在
nativeLookup::lookup
中也会做相同的事情。回去看
lookup
的代码,你会发现,假如我们先使用了
RegisterNatives
的方式注册了本地方法,那么是不需要走到
lookup_base
里面去查找动态链接库了。
set_native_function
的定义是在
class methodOopDesc : public oopDesc { friend class methodKlass; ...  // native function (used for native methods only)  enum {    native_bind_event_is_interesting = true  };  address native_function() const                { return *(native_function_addr()); }  address critical_native_function();  // Must specify a real function (not NULL).  // Use clear_native_function() to unregister.  void set_native_function(address function, bool post_event_flag);  bool has_native_function() const;  void clear_native_function();  ...}

也就是说每一个Java的native方法,最后会调用哪个本地方法,其实是被放到methodOopDesc::native_function中的,不管是按约定名称解析的方式,还是RegisterNatives的方式。其实在JNI规范中已经给出了RegisterNatives方式的说明,

The programmer can also call the JNI function RegisterNatives() to register the native methods associated with a class. The RegisterNatives() function is particularly useful with statically linked functions.

相对于查找动态链接库的方式,这是一种静态链接的方式。

回过头去看下给Thread.start0注册的本地方法JVM_StartThread,在中实现,

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))  JVMWrapper("JVM_StartThread");  JavaThread *native_thread = NULL;  // We cannot hold the Threads_lock when we throw an exception,  // due to rank ordering issues. Example:  we might need to grab the  // Heap_lock while we construct the exception.  bool throw_illegal_thread_state = false;  // We must release the Threads_lock before we can post a jvmti event  // in Thread::start.  {    // Ensure that the C++ Thread and OSThread structures aren't freed before    // we operate.    MutexLocker mu(Threads_lock);    // Since JDK 5 the java.lang.Thread threadStatus is used to prevent    // re-starting an already started thread, so we should usually find    // that the JavaThread is null. However for a JNI attached thread    // there is a small window between the Thread object being created    // (with its JavaThread set) and the update to its threadStatus, so we    // have to check for this    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {      throw_illegal_thread_state = true;    } else {      // We could also check the stillborn flag to see if this thread was already stopped, but      // for historical reasons we let the thread detect that itself when it starts running      jlong size =             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));      // Allocate the C++ Thread structure and create the native thread.  The      // stack size retrieved from java is signed, but the constructor takes      // size_t (an unsigned type), so avoid passing negative values which would      // result in really large stacks.      size_t sz = size > 0 ? (size_t) size : 0;      native_thread = new JavaThread(&thread_entry, sz);      // At this point it may be possible that no osthread was created for the      // JavaThread due to lack of memory. Check for this situation and throw      // an exception if necessary. Eventually we may want to change this so      // that we only grab the lock if the thread was created successfully -      // then we can also do this check and throw the exception in the      // JavaThread constructor.      if (native_thread->osthread() != NULL) {        // Note: the current thread is not being used within "prepare".        native_thread->prepare(jthread);      }    }  }  if (throw_illegal_thread_state) {    THROW(vmSymbols::java_lang_IllegalThreadStateException());  }  assert(native_thread != NULL, "Starting null thread?");  if (native_thread->osthread() == NULL) {    // No one should hold a reference to the 'native_thread'.    delete native_thread;    if (JvmtiExport::should_post_resource_exhausted()) {      JvmtiExport::post_resource_exhausted(        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,        "unable to create new native thread");    }    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),              "unable to create new native thread");  }  Thread::start(native_thread);JVM_END

FileChannelImpl.transferTo0

FileChannelImpl.transferTo0则是通过动态链接的方式实现,具体代码实现在中,

JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this,                                            jint srcFD,                                            jlong position, jlong count,                                            jint dstFD){#if defined(__linux__)    off64_t offset = (off64_t)position;    jlong n = sendfile64(dstFD, srcFD, &offset, (size_t)count);    if (n < 0) {        if (errno == EAGAIN)            return IOS_UNAVAILABLE;        if ((errno == EINVAL) && ((ssize_t)count >= 0))            return IOS_UNSUPPORTED_CASE;        if (errno == EINTR) {            return IOS_INTERRUPTED;        }        JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");        return IOS_THROWN;    }    return n;#elif defined (__solaris__)    sendfilevec64_t sfv;    size_t numBytes = 0;    jlong result;    sfv.sfv_fd = srcFD;    sfv.sfv_flag = 0;    sfv.sfv_off = (off64_t)position;    sfv.sfv_len = count;    result = sendfilev64(dstFD, &sfv, 1, &numBytes);    /* Solaris sendfilev() will return -1 even if some bytes have been     * transferred, so we check numBytes first.     */    if (numBytes > 0)        return numBytes;    if (result < 0) {        if (errno == EAGAIN)            return IOS_UNAVAILABLE;        if (errno == EOPNOTSUPP)            return IOS_UNSUPPORTED_CASE;        if ((errno == EINVAL) && ((ssize_t)count >= 0))            return IOS_UNSUPPORTED_CASE;        if (errno == EINTR)            return IOS_INTERRUPTED;        JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");        return IOS_THROWN;    }    return result;#elif defined(__APPLE__)    off_t numBytes;    int result;    numBytes = count;#ifdef __APPLE__    result = sendfile(srcFD, dstFD, position, &numBytes, NULL, 0);#endif    if (numBytes > 0)        return numBytes;    if (result == -1) {        if (errno == EAGAIN)            return IOS_UNAVAILABLE;        if (errno == EOPNOTSUPP || errno == ENOTSOCK || errno == ENOTCONN)            return IOS_UNSUPPORTED_CASE;        if ((errno == EINVAL) && ((ssize_t)count >= 0))            return IOS_UNSUPPORTED_CASE;        if (errno == EINTR)            return IOS_INTERRUPTED;        JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");        return IOS_THROWN;    }    return result;#else    return IOS_UNSUPPORTED_CASE;#endif}

转载地址:http://gguhl.baihongyu.com/

你可能感兴趣的文章
第二周编程总结
查看>>
【高斯消元】[JSOI2008]球形空间产生器sphere
查看>>
Response
查看>>
子网划分
查看>>
使用Hash直接登录Windows
查看>>
Oracle XML处理
查看>>
Java I/O Properties的使用 存取配置文件
查看>>
各种jar包下载地址
查看>>
解决win10激活错误代码0xc004c003
查看>>
个人编程作业1-GIT应用
查看>>
增加swap大小
查看>>
话说System.Object(读书笔记)
查看>>
ps查询进程
查看>>
Linux两块磁盘挂载指向一个文件夹LVM磁盘管理(三)
查看>>
使用消息队列的 10 个理由
查看>>
客户端验证码框架——jquery real person
查看>>
毕业了去哪里工作,一位毕业多年北漂人的经验感悟
查看>>
我的友情链接
查看>>
java EE5与javamail不兼容的解决办法
查看>>
长途猫的归属
查看>>