zoukankan      html  css  js  c++  java
  • JNI 基础用法相关总结

    JNI

    Sun JNI

    JNI oracle 详细文档

    • JNI overview

      • 为什么需要 jni?
        • 有了标准以后,native 库可移植。
          • 统一 java 和 native 的互操作接口,使得这个接口不会受具体的 JVM 实现的影响。早期有一些 JVM 规定了私有的与 native 的交互,JVM 之间的 native 操作不兼容。
        • 一些时间要求严格的操作,使用低层次 native 代码进行,牺牲高级语言的方便,提高性能。
    • JNI 知识脉络

      • 与 java 的交互
        • java 调用 native
          • jni 方法 load
          • 数据类型 / jni 方法格式
        • native 调用 java
          • jniEnv 提供的 API
            • 反射 : 真的反射 + (类 / 对象/string/array + 方法 / 属性)
            • 锁,JVM 信息
    • JNI design

      • JNI 暴露给 native 的是一个 Pointer.Java 调用 native 的方法时,这个 Pointer 作为一个参数(即 JNIEnv,提供了很多操作 JVM 的方法,这些方法叫作 JNI 方法)

        • Pointer->Pointer->Pointer 是一个指针数组,每个指针指向一个方法。
      • 编译/ 链接 /加载

        • java 代码使用System.loadLibrary来加载 native 代码到内存中。JVM 内部为每一个classLoader维护一个已加载的 native library list.
      • JNI 方法名称解析

        • Java_切割的全称类名_切割的方法名[__如果方法重载,切割的方法参数签名]
        • java 方法和 native 方法同名不算重载
        • Unicode 字符以及一些特别的符号(比如_ / ; / [等)会进行相应的转义,转义成_0/_1/...,因为方法名以及类名等不会以数字为开头(所以 JNI 方法名的_数字不会有多重含义)。
      • native 方法参数

        • JNIEnv*是一个 JVM 指针,提供 JVM 对 native 的各种功能:access 对象,读取 native 调用 JNI 产生的 exception 等。
        • 对于静态 native 方法:f(JNIEnv*,jobject,args...),第二个参数为调用的 class
        • 对于非静态方法:f(JNIEnv*,jobject,args...),第二个参数为调用的对象。
      • native 引用 JVM 对象

        • 基本类型数据是直接 copy value 的

        • 其余类型传递引用到 native。所以

          1. 对 JVM 来说:JVM 必须对传入 native 的对象引用(reference)做额外的计数,才能保证这些对象不被 gc 清除。
          2. 对 native 代码来说:native 代码必须在不需要引用后,主动通知 JVM。
        • 对于 native 代码来说,对象的 reference 有两种:globallocal refs

          • global reference
            • 由 native 代码保存的 JVM 对象引用。
            • 由 native 代码调用globalRef = (*env)->NewGlobalRef(env, localRef)申请,调用(*env)->DeleteGlobalRef(env, globalRef)声明不再引用。
          • local reference 。 没有主动调用的类型
            • 存在周期:从 native 代码开始到 native 代码返回。JVM 在调用 native 方法时主动维护一个 local reference table,保存所有 native 代码引用的 local reference 防止其对象被 gc 掉,在 native 方法结束后将这些 reference 清空,允许 gc 清除。native 代码可以主动调用(*env)->DeleteLocalRef(env, localRef)来允许 JVM 回收这个对象。
            • 包括范围
              • 传入 native 代码的参数;
              • native 代码返回的参数。
              • 包不包括 native 代码请求 JVM 生成的对象呢?
          • 被 native 代码持有的 local reference 和 global reference 都不会被 gc 掉。
        • native 代码获取对象的 property 和调用对象的 method

          • 步骤
            1. jmethodID mid = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);
            2. jdouble result = env->CallDoubleMethod(obj, mid, 10, str);
          • 获取到methodId后,可以继续继续调用env->CallXXXMethod来调用,但是如果调用时这个 class 已经被 JVM unload 了,会产生问题。所以最好每次都GetMethodId后再CallXXXMethod.
        • 错误异常处理

          • native 代码调用 JNI 方法时,JNI 方法可能会抛出错误。native 代码应该在调用 JNI 方法后,调用ExceptionOccurred()来检查是不是发生了错误,获取 pendingException,并且做相应的处理。如果没有处理 pendingException 就调用其他的 JNI 方法,即有可能会发生错误,只有少数几个 JNI 方法在有 pendingException 的情况下可以正常运行

          • native 代码调用 JNI 方法ExceptionClear()来表示 pendingException 已经被处理了。

          • native 代码 return 的时候,有未处理的 pendingException,会在 java 代码里 raise this pendingException

          • native 代码可以调用 JNI 方法ThrowNew(JNIEnv*, exceptionClassObject, exceptionMsgString)来主动 raise an exception.

          • 多线程时的异常处理。?一个线程需要在适当的时候调用ExceptionOccurred()来获取是否另一个线程有异常发生,这里我不能很好的理解。

      • JNI 数据类型

        • 基础类型
          • boolean -> jboolean;
          • byte -> jbyte
          • ...
        • 引用类型
          • jobject / jclass / jstring / jarray / jXXXarray / jthrowable
          • C++里面这些类型有父子继承关系,C 里面都是 jobject.
        • type signature
          • 基础类型:Zboolean Bbyte Cchar Sshort Iint Jlong Ffloat Ddouble
          • 类:L fully-qualified-class ;,比如Ljava/lang/String;
          • 数组:[type,比如[B
          • 方法: ( arg-types ) ret-type ,比如 (ILjava/lang/String;[I)J
        • Modified UTF-8 String
          • 一个 UTF-8 字符占 2 个 byte,Modified UTF-8 字符根据 UTF-8 的字符代码,占据 1 个 byte(ASCII),或者两个,或者三个。这里涉及到了编码的设计。
          • 这个 UTF-8 的设计可以使得 ASCII 字符只占据一个 byte,正式 UTF-8 占两个 byte,对于 ASCII 字符,节省空间。
      • JNI Functions

        • JNIEnv*是一个指针,指向一个包含所有 JNI 方法指针的struct
        • functions 分类具体的 jni functions.
          • Version Information
          • Class Operations
          • Exceptions
          • Global and Local References
          • Weak Global References
          • Object Operations
          • Accessing Fields of Objects
          • Calling Instance Methods
          • Accessing Static Fields
          • Calling Static Methods
          • String Operations
          • Array Operations
          • Registering Native Methods
          • Monitor Operations
          • NIO Support
          • Reflection Support
          • Java VM Interface
      • Invocation API 。 用来给独立的 native 代码(即不是从 java 的System.loadLibrary加载的 native 代码)操作 JVM 的 API

        • 可以主动新建一个JVM;让 JVM 加载一个指定的class;执行类的某些方法或者进行某些操作(就像一般的 native 代码执行 jvm 方法一样)。

        • JVM 加载 native library

          • jdk1.2 后,native library 跟自己类所在的 classLoader 绑定。一旦所在类的 classLoader 被卸载了,native library 也会被清除;一个 JVM 只能加载一个 native library 一次
          • native library 可以提供一个方法jint JNI_OnLoad(JavaVM *vm, void *reserved);System.loadLibrary时 JVM 主动调用,以获取 native library 要求的 JVM 版本号(比如JNI_VERSION_1_2这些都是已定义好的int常量)。
          • native library 可以提供一个方法void JNI_OnUnload(JavaVM *vm, void *reserved); 在包含 native library 的 class loader 被 gc 的时候由 JVM 主动调用,以让 native code 执行一些必要的内存清理工作(比如释放 global reference 等)。
        • Invocation API functions native 可以用来主动操作 JVM 的方法(全局方法,不需要调用env->XXX,需要在 native code 里#include <jni.h>)。

          • jint JNI_GetDefaultJavaVMInitArgs(void *vm_args); native code 调用这个方法来获取 JVM 的默认配置参数,vm_args是一个指向JavaVMInitArgs结构的指针。
          // JavaVMInitArgs结构
          typedef struct JavaVMInitArgs {
              jint version;
          
              jint nOptions;
              JavaVMOption *options;
              jboolean ignoreUnrecognized;
          } JavaVMInitArgs;
          
          // JavaVMOption
          typedef struct JavaVMOption {
              char *optionString;  /* the option as a string in the default platform encoding */
              void *extraInfo;
          } JavaVMOption;
          
          // JavaVM结构
          typedef const struct JNIInvokeInterface *JavaVM;
          const struct JNIInvokeInterface ... = {
              NULL,
              NULL,
              NULL,
          
              DestroyJavaVM,
              AttachCurrentThread,
              DetachCurrentThread,
          
              GetEnv,
          
              AttachCurrentThreadAsDaemon
          };
          
          
          • 从 JDK1.2 开始,不支持一个进程里有多个 JVM 了。所以下面的几个方法都受到影响(比如只能获得一个 JVM,或者是)

            • jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs); 获取创建的所有 JVM,放到vmBuf里。
            • jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args); 新建一个JavaVM,native code 所在的线程将会是 JVM 的主线程,参数p_env会用来放 JVM 主线程的JNI Interface,参数vm_args是指向JavaVMInitArgs结构的指针。
          • Java VM结构方法表中的方法:

            • jint DestroyJavaVM(JavaVM *vm);将当前线程 attatch 到 JVM 上,在该线程成为 JVM 的唯一用户线程时,退出该 JVM 并且释放其占有的资源。
            • jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args); 把当前线程加到 JVM 里,参数p_env接收加进去以后的JNI Interface的指针,thr_args是增加线程到 JVM 的参数,结构如下:
            typedef struct JavaVMAttachArgs {
                jint version;  /* must be at least JNI_VERSION_1_2 */
                char *name;    /* the name of the thread as a modified UTF-8 string, or NULL */
                jobject group; /* global ref of a ThreadGroup object, or NULL */
            } JavaVMAttachArgs
            
            • jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** p_env, void* args); 把当前线程作为 Daemon thread加到 JVM 里。如果已经加过了,单纯地设置p_env的值,不会改变已经添加的线程的 daemon 状态(即如果之前不是 daemon,调用这个方法并不会让其变成 daemon)
            • jint DetachCurrentThread(JavaVM *vm); 取消 JVM 里的当前线程,其占有的锁都会释放掉。
            • jint GetEnv(JavaVM *vm, void **env, jint version);获取 JVM 中当前线程对应的JNI Interfaceversion参数为要求的 JVM version,如果实际的 JVM 不支持指定的 version 的话(比如实际为1.1要求的却是1.2),会返回错误,

    Java 命令行中使用 jni

    1. 编写 java/kt 代码,注册 native 方法,在static代码块中执行System.loadLibrary(对于 kt 为 companion object 的 init block).
    2. 通过 java/kt 代码生成 class 文件
      • 使用javac或者kotlinc(在 AS 的 plugins 中有该工具),生成.class.
    3. 使用javah X.class对生成的.class文件,生成所需的 C header file , .h
    4. 编写 .h 对应的 .c 文件,在其中实现方法声明的方法。
    5. 调用 gcc -c X.c 来生成 .o 文件
    6. 调用 gcc -shared -o X.so X.o 来生成 .so文件,得到共享库。(在 linux 上为libXXX.so,在 Mac 上为libXXX.jnilib
    7. 调用 java 执行有 jni 参与的 java 类。
      • 使用-Djava.library.path=""来引用所有的 jni 库
      • 使用-cp来指定所需的 class 或者 jar.
      • java_command.md

    问题

    何时加载 so

    在调用 native 方法前的任何时间都可以.通常在类的 static 代码块中进行加载.

    jni 方法是如何进行注册的.

    • 静态注册 : 通过 javah 生成 .h 文件,实现其中的方法. 优点: 简单; 缺点 : 方法名长.
    • 动态注册 : 通过在 jNI_OnLoad 方法中调用 JNIEnv.registerNatives 来进行注册,其中参数有 java 方法名c 方法指针 的对应.

    jni 的 java 层和 c 层的参数类型如何转换? Integer 会转成什么类型? string 呢?

    • 除了 string/class/Throwable 外的 Object 都转成 jobject.
    • Integer 是 jobject , string 是 jstring

    JNIEnv 是线程相关的吗?

    • 是的, JNIEnv 是线程独立的.

    JNI 如何在 native 调用 java 的方法? 如何获取一个对象的属性?

    • 找到 jclass -> 通过 jniEnv.getMethodId(jclass, methodName, methodSig) 获取 jMethodID -> 通过 jniEnv.callVoidMethod(obj, methodId, params) ;
      对于 static 方法要使用 jniEnv.callStaticVoidMethod (可能是因为涉及到方法的分派)
    • 对象的成员变量需要用 getByyteField 等方法来获取.
  • 相关阅读:
    Parameter Binding in ASP.NET Web API
    Which HTTP methods match up to which CRUD methods?
    ErrorHandling in asp.net web api
    HttpStatusCode
    Autofac Getting Started(默认的构造函数注入)
    Autofac Controlling Scope and Lifetime
    luvit 被忽视的lua 高性能框架(仿nodejs)
    undefined与null的区别
    VsCode中使用Emmet神器快速编写HTML代码
    字符串匹配---KMP算法
  • 原文地址:https://www.cnblogs.com/wkmcyz/p/15154549.html
Copyright © 2011-2022 走看看