jni学习笔记--jni访问数组引用异常处理缓存策略

李樟清 李樟清     2022-12-15     784

关键词:

1. 访问数组

1. 基本类型的数组

package com.zeking.jni;

public class JniTest03 

    public native void getArray(int[] intArray);

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        int [] intArray = 99,66,77,33,22,88;
        jniTest03.getArray(intArray);
    
#include "com_zeking_jni_JniTest03.h"
#include <string.h>
#include <Windows.h>

/*
* Class:     com_zeking_jni_JniTest03
* Method:    getArray
* Signature: ([I)V
*/
// 访问基本类型数据数组
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_getArray
(JNIEnv * env, jobject jobj, jintArray jintArray)

    int compare(jint *a,jint *b);

    jint *elements  = (*env)->GetIntArrayElements(env, jintArray, NULL);

    if (elements == NULL)
        return;
    

    int length = (*env)->GetArrayLength(env, jintArray);

    int i = 0;
     for (i;i < length; i++)
        printf("排序前:第%d个值是:%d\\n",i,elements[i]);
    

    qsort(elements,length,sizeof(jint),compare);
    int j = 0;
    for (j; j < length; j++)
        printf("排序后:第%d个值是:%d\\n", j, elements[j]);
    

    (*env)->ReleaseIntArrayElements(env, jintArray, elements, JNI_COMMIT);



int compare(jint *a ,jint *b)
    return *a - *b;

2. 引用类型的数组

package com.zeking.jni;

public class JniTest03 

    public native String[] initStringArray(int size);

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        String[] arr = jniTest03.initStringArray(5);
        for(int i = 0; i< arr.length;i++)
            System.out.println(arr[i]);
        
    
/*
* Class:     com_zeking_jni_JniTest03
* Method:    initStringArray
* Signature: (I)[Ljava/lang/String;
*/
// 访问引用数据类型的数组
JNIEXPORT jobjectArray JNICALL Java_com_zeking_jni_JniTest03_initStringArray
(JNIEnv *env, jobject jobj, jint size)
    int i;
    jclass jclz = (*env)->FindClass(env, "java/lang/String");

    if (jclz == NULL)
        return NULL;
    
    // 创建jobjectArray
    jobjectArray result = (*env)->NewObjectArray(env, size, jclz, jobj);

    if (result == NULL)
        return NULL;
    
    // 赋值
    for (i = 0; i < size; i++)
        // C 字符串
        char * c_str = (char*)malloc(256);
        memset(c_str, 0, 256);
        // 将int 转换成为char
        sprintf(c_str, "hello num:%d\\n", i);
        // C ->jstring
        jstring str = (*env)->NewStringUTF(env, c_str);

        if (str == NULL)
            return NULL;
        
        // 将jstring 赋值给数组
        (*env)->SetObjectArrayElement(env, result, i, str);

        free(c_str);
        c_str = NULL;

    
    // 返回jobjectArray
    return result;

2. JNI引用

1. 局部引用

package com.zeking.jni;

public class JniTest03 

    public native void localRef();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        jniTest03.localRef();
    

/*
* Class:     com_zeking_jni_JniTest03
* Method:    localRef
* Signature: ()V
*/
// JNI 引用
// 局部引用
// 用来在JNI层生成JVM里面非基本类型数据结构的方法都是可以用于定义局部引用
// 定义方式多样:FindClass,NewObject,GetObjectClass,NewCharArray.... NewLocalRef()
// 释放方式: 1 方法调用完JVM 会自动释放 2.DeleteLocalRef
// 为什么 会自动释放,我们还要手动释放呢?在JNI里面会有一个 JNI局部引用表,
// 在ANDROID里面,整个android系统给 这个表 分配的内存他能够存储 比如 512个,
// 也就是说在android系统只能在同一时刻最多创建512个局部引用,每创建一个引用
// JVM就会把他放到这个JNI局部引用表里面来,如果没有手动及时释放,很可能创建的
// 引用一下子就达到了512个,音视频里面容易导致溢出
// 局部引用 : 不能在多线程里面使用,只能应用于定义的接口里面
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_localRef
(JNIEnv * env, jobject jobj)

    int i = 0;
    for (i = 0; i < 5; i++)
        jclass jclz = (*env)->FindClass(env, "java/util/Date");
        // 这个并不是引用,因为他没有定义一个对象,创建一个JVM里面的数据类型对象
        jmethodID jmid = (*env)->GetMethodID(env, jclz, "<init>", "()V");
        // 创建一个Date类型的局部引用
        jobject jobj = (*env)->NewObject(env, jclz, jmid);
        // 使用这个引用 ,下面代码省略

        // 释放这个引用
        (*env)->DeleteLocalRef(env, jclz);
        (*env)->DeleteLocalRef(env, jobj);

    

2. 全局引用

package com.zeking.jni;

public class JniTest03 

    public native void createGlobalRef();

    public native String getGlobalRef();

    public native void delGlobalRef();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        jniTest03.createGlobalRef();
        System.out.println(jniTest03.getGlobalRef());
        jniTest03.delGlobalRef();
        System.out.println("globalRef is released");
    
// 全局引用
// 跨进程,跨方法使用(可以多线程使用)
// NewGlobalRef 是创建全局引用的唯一方法
jstring global_str;
/*
* Class:     com_zeking_jni_JniTest03
* Method:    createGlobalRef
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_createGlobalRef
(JNIEnv * env, jobject jobj)
    jobject obj =(*env)->NewStringUTF(env, "JNI is intersting");
    global_str =  (*env)->NewGlobalRef(env, obj);


/*
* Class:     com_zeking_jni_JniTest03
* Method:    getGlobalRef
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_getGlobalRef
(JNIEnv * env, jobject jobj)
    return global_str;


/*
* Class:     com_zeking_jni_JniTest03
* Method:    delGlobalRef
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_delGlobalRef
(JNIEnv *env, jobject jobj)
    (*env)->DeleteGlobalRef(env, global_str);


//弱全局引用 
//它不会阻止GC,/跨线程,跨方法使用
//NewWeakGlobalRef 是创建弱全局引用的唯一方法
jclass g_weak_cls;
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_createWeakRef
(JNIEnv * env, jobject jobj) 
    jclass cls_string = (*env)->FindClass(env, "java/lang/String");
    g_weak_cls = (*env)->NewWeakGlobalRef(env, c ls_string);
    return g_weak_cls;

3. 异常

package com.zeking.jni;

public class JniTest03 

    public native void exception();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();

        try 
        // 如果不做处理,java是补获不到 exception,后面的java代码没办法继续执行
            jniMain.exception();
         catch (Exception e) 
            System.out.println(e.toString());
        

        System.out.println("----------------异常发生后-------------");     
    

/*
* Class:     com_zeking_jni_JniTest03
* Method:    exception
* Signature: ()V
*/
// 异常展示
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_exception
(JNIEnv * env, jobject jobj)
    jclass jlaz = (*env)->GetObjectClass(env, jobj);

    jfieldID fid = (*env)->GetFieldID(env, jlaz, "key","Ljava/string/String");

    printf("exception \\n");

下面是控制台输出的内容
当我们运行的时候会报错,
当我们在找 key 这个id 的时候 ,不存在
但是 printf(“exception \\n”); 还是打印出来了,如果是java的话,如果报了异常,我们不去捕获他,后面的代码没办法继续执行

Exception in thread "main" java.lang.NoSuchFieldError: key
    at com.zeking.jni.JniTest03.exception(Native Method)
    at com.zeking.jni.JniTest03.main(JniTest03.java:40)
exception 

如何让java层顺利的执行后面的代码呢?
如何让java层顺利的捕获到jni的异常呢?

/*
* Class:     com_zeking_jni_JniTest03
* Method:    exception
* Signature: ()V
*/
// 抛出异常
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_exception
(JNIEnv * env, jobject jobj)
    jclass cls = (*env)->GetObjectClass(env, jobj);
    jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");

    //检查是否发送异常,java 里面的检查是 try catch
    jthrowable ex = (*env)->ExceptionOccurred(env);
    // 判断异常是否发送 ,相当于 catch ,说明发生了异常
    if (ex != NULL) 
        jclass newExc;
        //清空JNI 产生的异常,如果只是这句代码,没有后面的,这样清空了代码,java层可以继续执行后面的代码
        //即可以打印出----------------异常发生后------------- 这个字符串
        (*env)->ExceptionClear(env);
        // 模拟 java中的  IllegalArgumentException
        newExc = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
        if (newExc == NULL)
        
            printf("exception newExc NULL \\n");
            return;
        
        (*env)->ThrowNew(env, newExc, "Throw exception from JNI: GetFieldID faild ");
    

结果打印

java.lang.IllegalArgumentException: The exception from JNI:GetFieldID failed
-----------发生异常后 ----------
exception 

后面会写一篇关于 异常的工具类的 文章,还有 打印log的工具类的文章

4. 缓存

1. 局部静态变量进行缓存

package com.zeking.jni;

public class JniTest03 

    public String key = "zeking";

    public native void cached();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        for (int i = 0; i < 5; i++) 
            jniTest03.cached();

        
    

/*
* Class:     com_zeking_jni_JniTest03
* Method:    cached
* Signature: ()V
*/
// 局部静态变量进行缓存.
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_cached
(JNIEnv * env, jobject jobj)
    jclass jclz = (*env)->GetObjectClass(env, jobj);
    // static 关键字就是告诉我们他是一个静态变量,如果定义的静态变量是一个局部变量
    // 这个局部变量会存储在 static的 静态存储区,
    // 意味着他只要定义初始化一次,以后在访问同一个静态区的时候,
    // 他会一直有定义,不会被释放
    static jfieldID fid = NULL;
    if (fid == NULL)
        fid = (*env)->GetFieldID(env, jclz,"key", "Ljava/lang/String;");
        printf("GetFieldId \\n");
    

2. 全局变量进行缓存

package com.zeking.jni;

public class JniTest03 

    public String key = "zeking";

    public native static void cachedGlobal();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        for (int i = 0; i < 5; i++) 
            cachedGlobal();
        
    
static jfieldID global_fid;// static 全局只能在这个定义的后面才能被使用,
/*
* Class:     com_zeking_jni_JniTest03
* Method:    cachedGlobal
* Signature: ()V
*/
// 全局变量
JNIEXPORT void JNICALL Java_com_zeking_jni_JniTest03_cachedGlobal
(JNIEnv * env, jclass jclz)

    if (global_fid == NULL)
        global_fid = (*env)->GetFieldID(env, jclz, "key", "Ljava/lang/String;");
        printf("GetFieldId Global\\n");
    

5. 缓存策略和弱引用联合使用带来的问题

package com.zeking.jni;

public class Refence 


    public int getRef(int num) 
        return num;
    

package com.zeking.jni;

public class JniTest03 

    public native String acessCacheNewString();

    public native String acessCF();

    static
        System.loadLibrary("JniTest03");
    

    public static void main(String[] args) 

        JniTest03 jniTest03 = new JniTest03();
        for(int i = 0 ; i < 10 ; i++)
            jniTest03.acessCF();
            long[] arr = new long[1024*1024];
        
    

/*
* Class:     com_zeking_jni_JniTest03
* Method:    acessCacheNewString
* Signature: ()Ljava/lang/String;
*/
// 缓存策略和弱引用联合使用带来的问题
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_acessCacheNewString
(JNIEnv * env , jobject jobj)
    // 定义一个静态的局部变量
    static jclass cls_string = NULL;
    if (cls_string == NULL)
        printf("in   Java_com_zeking_jni_JniTest03_acessCacheNewString \\n"); 
        // 给局部静态变量赋一个局部引用
        cls_string = (*env)->FindClass(env,"com/zeking/jni/Refence" );
    

    // 使用这个静态局部变量 ,, 第二次进来,这边可能出现了问题,因为这个cls_string 可能指向一个被JVM释放,指向一个野指针。
    jmethodID jmi = (*env)->GetMethodID(env, cls_string, "getRef", "(I)I");
    jthrowable ex = (*env)->ExceptionOccurred(env);

    if (ex != NULL)
        jclass newExc;
        // 让java继续运行
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
        printf("c exception happend\\n");
    

    printf("out   Java_com_zeking_jni_JniTest03_acessCacheNewString \\n");

    return NULL;


/*
* Class:     com_zeking_jni_JniTest03
* Method:    acessCF
* Signature: ()Ljava/lang/String;
*/
// 调用 acessCF 方法 其实他里面就是调用了acessCacheNewString方法,
// 当调用完 acessCF 方法之后,他里面的东西就被释放了,但是
// acessCacheNewString 中的cls_string 是一个  静态的 局部变量,他没有被释放
// 当我们再次调用 AcessCF 方法之后,cls_string 指向的是一个被释放的区域(指向一个野指针)
JNIEXPORT jstring JNICALL Java_com_zeking_jni_JniTest03_acessCF
(JNIEnv * env, jobject jobj)
    return Java_com_zeking_jni_JniTest03_acessCacheNewString(env, jobj);

下面就是控制台输出的内容 ,发生了异常

java.lang.NoSuchMethodError: getRef
    at com.zeking.jni.JniTest03.acessCF(Native Method)
    at com.zeking.jni.JniTest03.main(JniTest03.java:67)
java.lang.NoSuchMethodError: getRef
    at com.zeking.jni.JniTest03.acessCF(Native Method)
    at com.zeking.jni.JniTest03.main(JniTest03.java:67)
in   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
c exception happend
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
c exception happend
out   Java_com_zeking_jni_JniTest03_acessCacheNewString 
Exception in thread "main" Exception in thread "main" 

javaseapiknowhow-jni-异常-日志

JavaSEAPIknowhowJava原生接口(JavaNativeInterface)访问特殊的,操作系统特有的函数或者C/C++函数库就需要JNI跨越JNI边界的成本很高,调用现有C库首先要编写胶水代码(健壮性难以保证)如果涉及的参数类... 查看详情

intellijidea平台下jni编程—本地c代码创建java对象及引用(代码片段)

本文学习如何在C代码中创建Java对象和对象数组,前面我们学习了C代码中访问Java对象的属性和方法,其实在创建对象时本质上也就是调用构造函数,因此本文知识学习起来也很轻松。有了前面学习数组创建的方法后,C代码创建... 查看详情

jni学习笔记

JNI全称:javanativeinterfaceC字符串不检查下标越界 eclipse开发JNI较原始步骤(了解): ①写java代码声明本地方法用到native关键字本地方法不用去实现②项目根目录下创建jni文件夹③在jni文件夹下创建.c文件  Application.mk这个文... 查看详情

jni学习笔记

1为什么使用JNI?JNI的强大特性使我们在使用JAVA平台的同时,还可以重用原来的本地代码。作为虚拟机实现的一部分,JNI允许JAVA和本地代码间的双向交互。请记住,一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:1、程序不再... 查看详情

JNI 保持对对象的全局引用,并使用其他 JNI 方法访问它。在多个 JNI 调用中保持 C++ 对象处于活动状态

】JNI保持对对象的全局引用,并使用其他JNI方法访问它。在多个JNI调用中保持C++对象处于活动状态【英文标题】:JNIkeepingaglobalreferencetoanobject,accessingitwithotherJNImethods.KeepingaC++objectaliveacrossmultipleJNIcalls【发布时间】:2012-03-2023:05:1... 查看详情

混合编程jni第四篇之引用和异常

...继续介绍JNI的知识点 除八种基本数据类型之外的都是引用数据类型;关于引用Java虚拟机的内存结构我们都知道,堆内存和堆外内存大家都知道,Java代码创建的对象大多在堆内存内Native代码创建的对象 查看详情

jni/ndk开发指南——jni异常处理(代码片段)

异常简介异常,显而意见就是程序在运行期间没有按照正常的程序逻辑执行,在执行过程当中出现了某种错误,导致程序崩溃。在Java中异常分为运行时异常(RuntimeException)和编译时异常,在程序中有可能... 查看详情

混合编程jni第四篇之引用和异常(代码片段)

...天继续介绍JNI的知识点 除八种基本数据类型之外的都是引用数据类型;关于引用Java虚拟机的内存结构我们都知道,堆内存和堆外内存大家都知道,Java代码创建的对象大多在堆内存内Native代码创建的对象,占用的 查看详情

jni应用

...型转化成c能够处理的类型 c如何调用java的方法设置/访问java的属性传递三个参数数组的首地址图片的宽度图片的高度 c处理图片的方法processPic(int*,intwidth,intheight)大公司跟金融相关的/硬件相关 查看详情

使用 JNI 在 C 中访问 Java 对象中的 Java 对象

】使用JNI在C中访问Java对象中的Java对象【英文标题】:AccessingaJavaobjectinaJavaobjectinCusingJNI【发布时间】:2012-08-0322:04:53【问题描述】:我对JNI比较陌生,并且已经掌握了使用JNI在Java对象中处理整数和数组的基础知识。现在我正在... 查看详情

JNI 类 ID 段错误

...的函数内联的值时,错误消失了。我发现我需要使用全局引用,但这并没有解决段错误。与jni缓存相关的信息(略微过时的签名)-InJNI,howdoIcachetheclass,m 查看详情

android学习笔记----jni中的c控制java

面向对象的底层实现          java作为面向对象高级语言,可对现实世界进行建模。和面向过程不同的是面向对象软件的编写不是流程的堆积,而是对业务逻辑的多视角分解和分类。其过程大... 查看详情

调试 JNI 模块中的特定访问冲突

】调试JNI模块中的特定访问冲突【英文标题】:DebuggingspecificaccessviolationinJNImodule【发布时间】:2012-06-0709:02:35【问题描述】:我正在尝试通过带有WinDBG的JNI调试在基于Java的服务进程中运行的C++组件中的访问冲突错误。我目前面... 查看详情

22jni-动态注册与jni线程(代码片段)

AndroidNdk学习笔记(目录)//1静态注册native函数publicnativevoidstaticRegister();//---------------------------静态注册---------------------------------extern"C"JNIEXPORTvoidJNICALLJava_com_cn_mynat 查看详情

混合编程jni第五篇之c++访问java代码

 今天继续JNI的学习,因为是混合编程,所以在写的过程中需要进行交互Java可以调用C++,C++也可以调用Java,虽然作为Java程序很少写C++,但是既然是做JNI开发,就不得不了解下如果在C++中访问Java的属性和方法,开始吧访问属... 查看详情

混合编程jni第五篇之c++访问java代码(代码片段)

...程Jni】系列目录_香菜聊游戏的博客-CSDN博客今天继续JNI的学习,因为是混合编程,所以在写的过程中需要进行交互Java可以调用C++,C++也可以调用Java,虽然作为Java程序很少写C++,但是既然是做JNI开发,就不得不了解下如果在C++中... 查看详情

从 JNI 返回 MPI::Request[] 类型并访问 Java 中的 Request 元素

】从JNI返回MPI::Request[]类型并访问Java中的Request元素【英文标题】:ReturnMPI::Request[]typefromJNIandaccesselementsofRequestinJava【发布时间】:2015-02-2104:52:50【问题描述】:我希望将MPI::Request[]类型从原生C++函数返回给Java(通过JNI),然后... 查看详情

混合编程jni第九篇之jni总结

...数据类型,在本地代码和JVM之间进行复制传递,而对象是引用传递的。每一个引用都包含一个指向JVM中相应的对象的指针,但本地代码不能直接使用这个指针,必须通过引用来间接使用。局部引用和全局引用传递给原 查看详情