美文网首页
JNI 原理

JNI 原理

作者: 莫库施勒 | 来源:发表于2019-03-04 20:41 被阅读0次

    我们都知道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方法的时候,用刚才的例子中提到的方法进行调用。

    相关文章

      网友评论

          本文标题:JNI 原理

          本文链接:https://www.haomeiwen.com/subject/xekwuqtx.html