本文共 21309 字,大约阅读时间需要 71 分钟。
最近想看下HotSpotVM是怎么找到一个native方法的实现的,例如和,最后发现是两种不同的方式。
通过可以知道,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的系统调用,参考。
在有这么几行代码,
/* Make sure registerNatives is the first thing根据上面的规则,在libjava.so中应该存在does. */ private static native void registerNatives(); static { registerNatives(); }
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
则是通过动态链接的方式实现,具体代码实现在中,
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/