JNI是Java Native Interface的縮寫,它為java提供了調用C和C++代碼的能力。java.lang包下的很多類都用到了native方法,比如Class、String、Thread、System,可以說JNI是java語言的基礎。了解了JNI的實現原理,可以讓我們對java虛擬機有更深的認識。本文主要從源碼的角度,分析java虛擬機是如何實現對JNI的支持的。
1. native方法的調用
首先來看一下虛擬機是如何調用一個native方法的。
//dalvik2/vm/interp/Stack.cpp
Object* dvmInvokeMethod(Object* obj, const Method* method,
ArrayObject* argList, ArrayObject* params, ClassObject* returnType,
bool noAccessCheck)
{
//省略次要流程 ...
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
(*method->nativeFunc)((u4*)self->interpSave.curFrame, &retval,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, &retval);
}
上面的代碼就是dalvik虛擬機調用一個方法的實現,它發現如果這是一個native方法,則使用Method對象中的nativeFunc函數指針對象調用。那么nativeFunc函數指針指向哪個函數呢?
2. nativeFunc函數指針的賦值
nativeFunc主要有兩種情況
- 默認情況下,nativeFunc會指向一個解析方法;
- 也可以通過JNIEnv提供的注冊方法,手動建立native方法與實現方法的關系,此時nativeFunc就指向了實現方法。
2.1 本地方法解析resolveNativeMethod
// dalvik2/vm/oo/Class.cpp
findClassNoInit
loadClassFromDex
loadClassFromDex0
loadMethodFromDex
static void loadMethodFromDex(ClassObject* clazz, const DexMethod* pDexMethod, Method* meth) {
...
if (dvmIsNativeMethod(meth)) {
meth->nativeFunc = dvmResolveNativeMethod;
meth->jniArgInfo = computeJniArgInfo(&meth->prototype);
}
}
第一次從dex中加載類時,會加載類中的所有方法,對于native方法,會將nativeFunc賦值為dvmResolveNativeMethod,dvmResolveNativeMethod方法如下:
// dalvik2/vm/Native.cpp
void dvmResolveNativeMethod(const u4* args, JValue* pResult,
const Method* method, Thread* self) {
ClassObject* clazz = method->clazz;
//對于靜態方法,要確保所屬的class已經初始化
if (dvmIsStaticMethod(method)) {
if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
assert(dvmCheckException(dvmThreadSelf()));
return;
}
} else {
assert(dvmIsClassInitialized(clazz) ||
dvmIsClassInitializing(clazz));
}
//在內部本地方法表中查詢
DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
if (infunc != NULL) {
DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
dvmSetNativeFunc((Method*) method, dfunc, NULL);
dfunc(args, pResult, method, self);
return;
}
//在動態鏈接庫表中查詢
void* func = lookupSharedLibMethod(method);
if (func != NULL) {
/* found it, point it at the JNI bridge and then call it */
dvmUseJNIBridge((Method*) method, func);
(*method->nativeFunc)(args, pResult, method, self);
return;
}
}
主要流程如下:
- 如果是靜態方法,首先確保所屬的class已經初始化
- 在內部靜態方法表中查詢
- 在動態鏈接庫中查詢
這個方法很巧妙,第一次調用native方法時,由于不知道其具體實現,所以就用resolveNativeMethod去查找,等找到后,就將nativeFunc替換為找到的函數,這樣下次再用的時候就省去了查找的過程。
2.1.1 內部本地方法的查詢
內部本地方法表是一個集合,這個集合里包含了java語言和虛擬機本身用到的native方法,如下所示:
// dalvik2/vm/native/InternalNative.cpp
static DalvikNativeClass gDvmNativeMethodSet[] = {
{ "Ljava/lang/Object;", dvm_java_lang_Object, 0 },
{ "Ljava/lang/Class;", dvm_java_lang_Class, 0 },
...
{ "Ljava/lang/String;", dvm_java_lang_String, 0 },
{ "Ljava/lang/System;", dvm_java_lang_System, 0 },
{ "Ljava/lang/Throwable;", dvm_java_lang_Throwable, 0 },
{ "Ljava/lang/VMClassLoader;", dvm_java_lang_VMClassLoader, 0 },
{ "Ljava/lang/VMThread;", dvm_java_lang_VMThread, 0 },
{ "Ldalvik/system/DexFile;", dvm_dalvik_system_DexFile, 0 },
{ "Ldalvik/system/VMRuntime;", dvm_dalvik_system_VMRuntime, 0 },
{ "Ldalvik/system/Zygote;", dvm_dalvik_system_Zygote, 0 },
{ "Ldalvik/system/VMStack;", dvm_dalvik_system_VMStack, 0 },
...
{ NULL, NULL, 0 },
};
DalvikNativeClass結構的定義如下:
// dalvik2/vm/Native.h
struct DalvikNativeClass {
const char* classDescriptor; //類描述符
const DalvikNativeMethod* methodInfo; //native方法數組
u4 classDescriptorHash; // 類描述符的hash值
};
類描述符的hash值,會在虛擬機啟動的時候進行計算,使用hash值的目的,是為了加快查找過程。
methodInfo是一個以NULL結尾的本地方法數組。DalvikNativeMethod定義如下:
struct DalvikNativeMethod {
const char* name; //方法名
const char* signature; // 方法簽名
DalvikNativeFunc fnPtr; //native函數
};
上面的dvm_java_lang_String就是String類中用到的所有native方法的數組,具體如下:
// dalvik2/vm/native/String.cpp
const DalvikNativeMethod dvm_java_lang_String[] = {
{ "charAt", "(I)C", String_charAt },
{ "compareTo", "(Ljava/lang/String;)I", String_compareTo },
{ "equals", "(Ljava/lang/Object;)Z", String_equals },
{ "fastIndexOf", "(II)I", String_fastIndexOf },
{ "intern", "()Ljava/lang/String;", String_intern },
{ "isEmpty", "()Z", String_isEmpty },
{ "length", "()I", String_length },
{ NULL, NULL, NULL },
};
2.1.2 動態鏈接庫的查詢
如果沒有在內部本地方法表中找到對應的native方法,則要繼續在動態鏈接庫中查找。這個動態鏈接庫就是通過System.loadLibrary加載的so,虛擬機會將所有的so存入一個表中,下面看看具體的代碼。
// dalvik2/vm/Native.cpp
static void* lookupSharedLibMethod(const Method* method)
{
return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,
(void*) method);
}
gDvm.nativeLibs是一個HashTable,其中存的是動態鏈接庫的信息。其中的數據是通過System.loadLibrary添加進去的,java層的調用關系如下:
System.loadLibrary
Runtime.loadLibrary0
Runtime.doLoad()
Rumtime.nativeLoad(name, loader, librarySearchPath)
Runtime.nativeLoad是個native方法,對應的native方法如下:
// dalvik2/vm/native/java_lang_Runtime.cpp
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult) {
StringObject* fileNameObj = (StringObject*) args[0];
Object* classLoader = (Object*) args[1];
StringObject* ldLibraryPathObj = (StringObject*) args[2];
...
bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
}
// dalvik2/vm/Native.cpp
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
char** detail) {
SharedLib* pEntry;
void* handle;
bool verbose;
...
//先檢查是否已經加載過
pEntry = findSharedLibEntry(pathName);
if (pEntry != NULL) {
// 檢查classLoader是否相同,如果不同會報錯,同一個so只能由一個classLoader加載
if (pEntry->classLoader != classLoader) {
ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
pathName, pEntry->classLoader, classLoader);
return false;
}
if (verbose) {
ALOGD("Shared lib '%s' already loaded in same CL %p",
pathName, classLoader);
}
if (!checkOnLoadResult(pEntry))
return false;
return true;
}
// 打開so文件
handle = dlopen(pathName, RTLD_LAZY);
// 創建一個新的ShareLib對象
SharedLib* pNewEntry;
pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
pNewEntry->pathName = strdup(pathName);
pNewEntry->handle = handle;
pNewEntry->classLoader = classLoader;
// 將新的ShareLib加到hash表中
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
if (pNewEntry != pActualEntry) {
ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
pathName, classLoader);
freeSharedLibEntry(pNewEntry);
return checkOnLoadResult(pActualEntry);
} else {
vonLoad = dlsym(handle, "JNI_OnLoad");
if (vonLoad == NULL) {
ALOGD("No JNI_OnLoad found in %s %p, skipping init",
pathName, classLoader);
} else {
OnLoadFunc func = (OnLoadFunc)vonLoad;
Object* prevOverride = self->classLoaderOverride;
self->classLoaderOverride = classLoader;
//調用JNI_OnLoad方法
version = (*func)(gDvmJni.jniVm, NULL);
}
}
}
}
上面的主要流程就是
- 檢查是否已經加載過該so,如果是還需要判斷加載so的classLoader是否相同,不同的話也會報錯,
- 如果沒有加載過,則使用dlopen打開so,然后創建一個ShareLib加入到gDvm.nativeLibs哈希表中。
- 如果so中有JNI_OnLoad方法,則執行該方法。我們可以在該方法中做一些初始化工作,還可以手動建立java類中的native方法和so中的native方法的對應關系。
findMethodInLib的主要流程是,首先根據規則,生成對應的native方法名,然后使用dlsym查找so中是否有該方法。
java層的native方法和so中的對應關系為:Java_類全名_方法名。
2.2 手動注冊native方法
上面提到System.loadLibrary加載so時,會執行其中的JNI_OnLoad方法,我們可以在該方法中手動注冊native方法。
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
const JNINativeMethod* methods, jint nMethods)
{
ScopedJniThreadState ts(env);
ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
if (gDvm.verboseJni) {
ALOGI("[Registering JNI native methods for class %s]",
clazz->descriptor);
}
for (int i = 0; i < nMethods; i++) {
if (!dvmRegisterJNIMethod(clazz, methods[i].name,
methods[i].signature, methods[i].fnPtr))
{
return JNI_ERR;
}
}
return JNI_OK;
}
針對每個JNINativeMethod,調用dvmRegiseterJNIMethod,該方法會修改Method對象的nativeFunc指針,從而建立和native實現方法的對應關系。
總結
本文分析了native方法的注冊與調用流程。
native方法既可以手動注冊,也可以按規則動態解析。
手動注冊,需要在JNI_OnLoad中使用JNIEnv提供的RegisterNatives接口注冊。
動態解析只在第一次調用時進行。