目录
常规类型的传递
这部分算是 JNI 的基本内容, 理所当然的有一大坨接口来干这些事情,
比如 NewString
, GetStringChars
, GetArrayLength
, NewByteArray
到 Java 层自然就是原生的数据类型了, 比如 String
, int
, byte[]
需要注意的只是, 有的类型不需要释放, 有的类型则需要, 例如对象可能需要 DeleteLocalRef
或 DeleteGlobalRef
,
访问数组内容一般需要 ReleaseByteArrayElements
Java 对象的内存管理
Java 使用类似引用计数的方式管理内存, 并且不定期 GC, 所以 JNI 访问 Java 对象还需要特别注意
临时对象
大多数情况来说, 访问方式一般都是 JNI 调用 Java 接口, 返回一个 Java 的临时对象, 例如:
// Java
public Object func() {...}
// JNI
jobject obj = env->CallObjectMethod(...);
这种情况一般不同担心, JNI 端代码结束后, 一般会在合适时机 GC
当然, 特殊情况是, 假如 JNI 端需要持续执行较长时间, 并且可能访问了较多的 Java 端对象,
就需要手动调用 DeleteLocalRef
释放这些临时对象, 避免性能问题, 此外, JNI 端 local 对象个数也是有限制的
同理的还有 NewString
等 JNI 接口, 创建的都是临时对象
全局对象
上面提到了, 临时对象会在不确定时机被 GC, 所以如果你要长期使用这个对象, 简单的保存 jobject 是不行的
正确的做法是, 使用 NewGlobalRef
来创建一个全局引用, 这个引用会一直存在, 不会被 GC, 直到你调用 DeleteGlobalRef
这边需要注意的是, NewGlobalRef
虽然是操作同一个对象, 但是 jobject 本身是不一样的, 典型的使用方法是:
jobject localRef = xxx;
jobject globalRef = env->NewGlobalRef(localRef);
env->DeleteLocalRef(localRef);
特殊类型的传递
对象和基础类型的传递一般都没什么问题, 麻烦事在于一大段内存块的传递
指针传递
这是比较常见也比较容易实现的方式, 典型使用场景是, 内存块的申请/使用/释放 (也可以是 C++ 对象), 都在 JNI 端处理, Java 端只负责调用和这个指针的传递
很常规的做法就是把指针转换为 jlong
进行传递即可
PS:
- 有点担心哪天升级 128 位 CPU 了咋办, 不过目前大多 JNI 都是这种处理方式, 应该未来会有变通方式解决吧
- 更保险起见的方式是将指针转换为
byte[]
进行传递, 不过麻烦而且性能会受影响
内存块的传递
典型使用场景:
Java 读取图片或音频什么的, 把内容作为内存块交给 JNI 处理, JNI 处理完后, 把新的内存块内容返回给 Java 端再重新解析为图片或音频什么的
(这里姑且不论使用临时文件做中转的方式, 何况写文件也挺慢的呢)
这个可谓麻烦至极, 首先, 通常都会想到用 byte[]
传递,
然而这货无论是 Java 传到 JNI 还是 JNI 传到 Java, 都 (可能但不一定) 需要进行深拷贝,
对于分分钟上兆的媒体文件来说, 是个巨大的 CPU 和内存开销
找遍文档, 避免深拷贝的方法大概有
GetPrimitiveArrayCritical
看上去好像那么一回事, 写个简单的测试代码发现可以用, 皆大欢喜了?
图样图森破, 当你尝试在 GetPrimitiveArrayCritical 和 ReleasePrimitiveArrayCritical 之间
再调用 Java 代码时, 就会发现挂了
顾名思义, 这货是直接访问 Java 的底层数据内容, 对于 Java 这种不知何时会 GC 的运行时来说,
Java 显然不会让你瞎搞, 试想以下流程:
- GetPrimitiveArrayCritical 得到底层数据指针
- JNI 端调用 Java 代码, 很可能产生 GC
- 底层数据指针所属的 Java 对象被 GC 了, 这个指针自然也就无效了
所以, 这个只适合用于只读而且逻辑简单的场景, 就像多线程编程通常不推荐在被锁的代码块里面做太多事情一样
(否则可能一不小心就死锁了)
ByteBuffer
仔细查找文档可以发现 NewDirectByteBuffer
这么个东东, 对应的是 Java 端的 java.nio.ByteBuffer
有了 ByteBuffer, JNI 端就可以通过 GetDirectBufferAddress
获得内存地址, 完美了… 吗?
图样图森破, 这货依然有着麻烦的使用条件:
-
虽然都叫
ByteBuffer
, 但是这个只能使用NewDirectByteBuffer
或者ByteBuffer.allocateDirect()
进行创建,
否则GetDirectBufferAddress
返回的总是 NULL不支持的几个:
ByteBuffer.allocate()
,ByteBuffer.wrap()
更具体的原因, 各位有兴趣可以去搜搜
DirectByteBuffer
和HeapByteBuffer
-
JNI 这魂淡没有提供足够的方法去操作 ByteBuffer 的方法
比如
position()
,remaining()
,flip()
都没有, 而只有GetDirectBufferCapacity
来获取最大容量,
所以你还得自行添加一大坨内容, 来在 JNI 调用这些方法
不管怎样, 最终我们可以用 ByteBuffer 来开心的玩耍了, 然而这货本身作为一个比较底层的 buffer, 提供的功能挺少,
于是又得自己管理内存增长之类的逻辑了, 怎样, 有没有一种回到 C 语言的美好感觉了?
over
转载请注明来自: http://zsaber.com/blog/p/107
既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)