java jni 原理分析_【源码解读】JNI的实现原理

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接口注册。

动态解析只在第一次调用时进行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值