• 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 等方法来获取.
  • 相关阅读:
    SGU 107
    HDU 1724 自适应辛普森法
    POJ 1061 扩展欧几里得
    zzuli2424: 越靠近,越幸运(dfs)
    zzuli1519: 小P参加相亲大会(异或)
    zzuli1519: 小P参加相亲大会(异或)
    牛客练习赛42 A:字符串
    牛客练习赛42 A:字符串
    zzuli1511: 小P的loI
    zzuli1511: 小P的loI
  • 原文地址:https://www.cnblogs.com/wkmcyz/p/15154549.html
Copyright © 2020-2023  润新知