JNI 常见异常分析

JNI基础内容:

编程语言间的互相调用可以有几种方案:

  • 通过RPC调用来实现跨语言的调用,不同语言编写的程序运行在不同的进程中,通过网络、输入输出流、管道等设施。
  • 通过共享内存实现进程间通信
  • 通过FFI(Foreign Function Interface)机制,通过提供运行时兼容的设施(一般是lib及运行时支持)实现在同一进程内跨越语言的边界调用函数,例如Java的JNI就是这样一种设施

JNI 技术背景:

运行环境:

基本数据类型:

引用数据类型:

方法签名:

Java/C 如何相互调用:

方法签名:

Jvm/JniEvn环境:

JNIEnv一般是是由虚拟机传入,而且与线程相关的变量,线程能操作的线程上下文 与 方法

JavaVM是虚拟机在JNI中的表示,一个JVM中只有一个JavaVM实例,这个实例是线程共享的

So 异常分析:

bash 复制代码
java.lang.UnsatisfiedLinkError: dlopen failed: library "/Users/user/Soft/Custom/dianxin/MH_ICT/app/src/main/cpp/lib/arm64-v8a/libcjson.so" not found: needed by /data/app/~~-fvxvo2CY1xGw_Zq_z_xjw==/com.dss.ict-tzmJw185jIwlMorqbPpxpw==/lib/arm64/libictProxy.so in namespace classloader-namespace

/Users/user/Library/Android/sdk/ndk/21.4.7075529/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-readelf -d /Users/user/Downloads/app-general-debug/lib/arm64-v8a/libictProxy.so

方法异常问题 :

% objdump -T ./libonnx_decoder.so | grep "OrtGetApiBase"0000000000000000 DF UND 0000000000000000 (VERS_1.12.0) OrtGetApiBase

% objdump -T ./libonnxruntime.so | grep "OrtGetApiBase" 000000000045c51c g DF .text 000000000000000c VERS_1.19.0 OrtGetApiBase

so name 问题:

正常so

系统so 加载错误问题:

系统权限应用加载:

安卓默认会按照优先级搜索下面的路径:

  • 应用的安装目录如上面的(/data/app/xxx/lib/arm64/)
  • so文件的RPATH字段指的的目录
  • LD_LIBRARY_PATH环境变量指定的目录
  • so文件的RUNPATH字段指的的目录
  • 系统目录如/system/lib64/、/vendor/lib64/、/system/apex/com.android.i18n/lib64/等

加载后缀问题:

bash 复制代码
java.lang.UnsatisfiedLinkError: dlopen failed: library "libcurl.so.4" not found: needed by /data/app/~~gCIOmXUrd8VmBqA43J-WWw==/com.dss.ict-375hPFYMdMCuKeYl4ymVAg==/lib/arm64/libictProxy.so in namespace classloader-namespace

安卓系统是支持这种加载带版本后缀的so,但是gradle在编译apk的时候确是只会将.so后缀的文件打包到apk,所以安装之后就缺失了这个so。

在Android上库不是在系统范围内安装的它们总是应用程序包的一部分,所以so的版本标记是不必要的,谷歌就把这块在打包的时候去掉了,但这样的差异造成了在安卓上引入第三方c/c++库方面需要对so的版本号进行额外的处理。

开发过程问题

线程问题:

因为通过AttachCurrentThread附加到虚拟机 的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类

引用数量问题:

lua 复制代码
JNI ERROR (app bug): local reference table overflow (max=512)

1 在某个本地方法调用中,创建的局部引用的数量不宜过多,否则可能导致JNI内部的局部引用表溢出,应该适时释放

2 不再使用的局部引用。尽管在本地方法返回后,局部引用会被自动释放,但是在这之前若不主动释放,局部引用所引用的对象会一直存活,可能导致使用的内存在方法调用时居高不下,甚至可能出现OOM的风险。

3 当一个本地方法不再返回,也就是内部无限循环执行,此时适时释放无用局部引用是必须的。例如,一个本地方法可能进入一个无终止的事件派遣循环。释放在循环中创建的局部引用至关重要(crucial),因此不会让引用数量累增而导致内存泄露

4 局部引用引用了一个较大的对象。在本地方法调用时,可能会访问java中大对象,局部引用在被释放之前会一直阻止JVM回收这个大对象,即是之后不再使用该对象,造成了内存浪费,因此适时释放该局部引用是合理的选择。 使用NewLocalRef/ DeleteLocalRef 来使用局部引用

相关推荐
东北南西10 小时前
实现 TypeScript 内置工具类型(源码解析与实现)
前端·typescript
learning_tom10 小时前
HTML5 标题标签、段落、换行和水平线
前端·html·html5
IT_陈寒10 小时前
Python性能优化:这5个隐藏技巧让我的代码提速300%!
前端·人工智能·后端
Dolphin_海豚10 小时前
【译】Reading vuejs/core-vapor - 中卷
前端·掘金翻译计划·vapor
只与明月听10 小时前
前端缓存知多少
前端·面试·html
Dolphin_海豚11 小时前
【译】Vue.js 下一代实现指南 - 下卷
前端·掘金翻译计划·vapor
Apifox11 小时前
理解和掌握 Apifox 中的变量(临时、环境、模块、全局变量等)
前端·后端·测试
小白_ysf11 小时前
阿里云日志服务之WebTracking 小程序端 JavaScript SDK (阿里SDK埋点和原生uni.request请求冲突问题)
前端·微信小程序·uni-app·埋点·阿里云日志服务
你的电影很有趣11 小时前
lesson52:CSS进阶指南:雪碧图与边框技术的创新应用
前端·css
Jerry11 小时前
Compose 延迟布局
前端