JNI 原理
我们都知道JNI结构是 Java 层 -> JNI -> Native 层, 以此实现Java 层和Native层可以互相调用
进程与DalivVM
每个应用程序都是由一个或多个进程组成,每个进程都对应着一个DalvikVM。DalvikVM是由代码native启动,在DalvikVM启动后,会返回一个JavaVM结构体。每个线程又对应着一个JNIEnv的结构体。也就是说整个进程都在native的管理之下,所以native可以非常容易的改变DalvikVM内部的数据。
总结
程序与进程是1:N,进程与DalivkVM是1:1,启动DalivkVM后会得到一个JavaVM,同时一个进程又对应n个线程,线程对应着一个JNIEnv的结构体,我们需要通过JavaVM和JNIEnv的机构来实现相互的访问,而JNIEnv内部包含一个Pointer来指向虚拟机的Function Table
实现
Java -> Native Android中每当一个java线程第一次要调用本地C/C++代码时,Dalvik虚拟机实例会为该java线程产生一个JNIEnv*指针
Native -> Java 本地的C/C++代码想获得当前线程所要使用的JNIEnv时,可以使用Dalvik VM对象的 JavaVMjvm -> GetEnv()方法,该方法会返回当前线程所在的JNIEnv。
Java <--> Native Java每个线程在和C/C++互相调用时,JNIEnv*是相互独立的,互不干扰的,提升了并发执行时的安全性。
具体分析
public class Test{
static{
System.loadLibrary("bridge");
}
public native int nativeAdd(int x,int y);
public int add(int x,int y){
return x+y;
}
public static void main(String[] args){
Test obj = new Test();
System.out.printf("%d\n",obj.nativeAdd(2012,3));
System.out.printf("%d\n",obj.add(2012,3));
}
}
我们这里有两个 add 方法,一个是 native 方法,一个是 Java 方法,我们查看一下它们在 class 中的字节码
public native int nativeAdd(int, int);
flags: ACC_PUBLIC, ACC_NATIVE
public int add(int, int);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 8: 0
普通的“add”方法是直接把字节码放到code属性表中,Java在执行的时候,可以通过找方法表,再找到相应的code属性表,最终解释执行代码。
而native方法,与普通的方法通过一个标志“ACC_NATIVE”区分开来。那么在执行的时候是怎么找到动态链接库的代码呢.
我们从 System.loadLibrary()
开始
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
synchronized void loadLibrary0(Class<?> fromClass, String libname) {
// ......中间省略......
ClassLoader.loadLibrary(fromClass, libname, false);
}
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class<?> fromClass, String name,
boolean isAbsolute) {
......
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
if (isAbsolute) {
if (loadLibrary0(fromClass, new File(name))) {
return;
}
......
}
if (loader != null) {
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
......
if (loadLibrary0(fromClass, libfile)) {
return;
}
......
}
}
for (int i = 0 ; i < sys_paths.length ; i++) {
......
if (loadLibrary0(fromClass, libfile)) {
return;
}
......
}
for (int i = 0 ; i < usr_paths.length ; i++) {
......
if (loadLibrary0(fromClass, libfile)) {
return;
}
......
}
......
}
private static boolean loadLibrary0(Class<?> fromClass, final File file) {
......
Vector<NativeLibrary> libs =
loader != null ? loader.nativeLibraries : systemNativeLibraries;
synchronized (libs) {
int size = libs.size();
for (int i = 0; i < size; i++) {
......
}
synchronized (loadedLibraryNames) {
......
int n = nativeLibraryContext.size();
for (int i = 0; i < n; i++) {
......
}
NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
nativeLibraryContext.push(lib);
try {
lib.load(name, isBuiltin);
} finally {
nativeLibraryContext.pop();
}
......
}
}
}
static class NativeLibrary {
// opaque handle to native library, used in native code.
long handle;
......
// the loader this native library belongs.
private final Class<?> fromClass;
// the canonicalized name of the native library.
// or static library name
String name;
// Indicates if the native library is linked into the VM
boolean isBuiltin;
// Indicates if the native library is loaded
boolean loaded;
native void load(String name, boolean isBuiltin);
native long find(String name);
native void unload(String name, boolean isBuiltin);
......
}
我们发现 NativeLibrary 也是调用的 native 函数。我们从源码查找 load,find,unload 的 native 实现
// openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void * os::dll_load(const char *filename, char *ebuf, int ebuflen)
{
......
bool load_attempted = false;
......
if (!load_attempted) {
result = os::Linux::dlopen_helper(filename, ebuf, ebuflen);
}
if (result != NULL) {
// Successful loading
return result;
}
......
}
// openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) {
void * result = ::dlopen(filename, RTLD_LAZY);
......
return result;
}
// /usr/include/dlfcn.h
/* Open the shared object FILE and map it in; return a handle that can be
passed to `dlsym' to get symbol values from it. */
extern void *dlopen (const char *__file, int __mode) __THROW;
简单的说,dlopen、dlsym提供一种动态转载库到内存的机制,在需要的时候,可以调用库中的方法。
dlopen,dlsym 用法
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//这里为了演示方便去掉错误检查相关的代码
int main(int argc,char*argv[]){
void * handle;
int (*func)(int,int);
char *error;
int a,b;
//加载libhello.so库
handle = dlopen("libhello.so",RTLD_LAZY);
func = dlsym(handle,"add");
//读取两个参数
sscanf(argv[1],"%d",&a);
sscanf(argv[2],"%d",&b);
//输出结果
printf("a + b = %d\n",(*func)(a,b));
dlclose(handle);
}
总结
dlopen相当于打开一个共享库,打开的时候可以使用RTLD_LAZY标志,等函数真正被调用的时候才去装载库。dlopen返回一个handle,这个句柄是使用其它的dlxxx函数用的,dlsym就是使用dlopen返回的handle,去查找相应的符号,然后返回相应的函数指针的。
如果回头去看看刚才分析的Java的NativeLibrary类,会发现里面有个handle成员!
Java动态装载共享库,靠的就是系统的 “dlxxx” 相关的函数实现的,在装载共享库的时候,读取共享库的 header,解析并保存里面的符号表,当调用native方法的时候,用刚才的例子中提到的方法进行调用。