面试题十四:JNI如何实现数据传递?
JNI如何实现数据传递?
一、面试官视角:这道题想考察什么?
- 是否有Native开发经验
- 是否对JNI数据传递中的细节有认识
- 是否能够合理的设计JNI的界限
二、题目剖析
1、传递什么数据?
2、如何实现内存回收?
3、性能如何?
4、结合实例来分析更有效
举例一:在Java层有个Bitmap(Bitmap.java
)类,在Native层也有一个类(Bitmap.h/cpp
)与之对应,那么这两者如何关联起来呢?
通过在Java层
Bitmap.java
持有的private final long mNativePtr
(指针),这个mNativePtr
就是Native层的Bitmap.h/cpp
的指针。
1 |
|
举例二:字符串操作
GetStringUTFChars/ReleaseStringUTFChars
const char*
拷贝出Modified-UTF-8的字节流(字节码存字符串使用MUTF-8)
\0编码成0xC080,不会影响C字符串结尾
const char* GetStringUTFChars(jstring string, jboolean* isCopy)
isCopy:并不是要你指定是否要拷贝,而是要你传进去一个引用,然后它会把Get之后的结果是不是拷贝的告诉你,本质上isCopy是一个返回值。
在调用完
GetStringUTFChars
方法之后,*isCopy == false
,表示没有复制,为什么?比如在Java虚拟机里面有个”HelloWorld”,那么Native函数中的
const char*
指向的就是它,假设内存GC了,GC之后一般都要进行整理,GC的算法有些就是要标记、整理和复制,这样做主要是为了减少内存碎片,让内存更加的连续,从而适应大对象的分配,但这个时候你因为要复制这个”HelloWorld”对象的话,就意味着要把这个对象锁定,为啥要锁定,因为const char*
指针是直接指向它的,这样的话,就不能GC了,因为一旦GC,这个指针不就跑了嘛!那你就指向了一个野指针,所以这个就要看虚拟机支持不支持,有些虚拟机还真就不支持,它们倾向于复制,而不是通过指针直接指向,因为可以减少它的逻辑,让它更轻松一些,所以很多虚拟机都会返回一个*isCopy == true
,那么这种情况就是,它帮你复制一分”HelloWorld”这块内存过来,然后指针指向的就是C层的一块内存了,跟Java虚拟机内存没有了任何关系了,Jvm GC就GC呗,无所谓!。
GetStringChars/ReleaseStringChars
- const jchar*(Java中的两个char对应一个jchar)
- JNI函数自动处理字节序转换
- const char* GetStringChars(jstring string, jboolean* isCopy)
GetStringUTFRegion/getStringRegion
- 先在C层创建足够容量的空间
- 将字符串的某一部分复制到开辟好的空间
- 针对性复制,少量读取时效率更优
GetStringCritical/ReleaseStringCritical
调用对中间会停掉 Jvm GC
调用对之间不可有其他操作
调用对可嵌套
const jchar* GetStringCritical(jstring string, jboolean* isCopy)
在调用完
GetStringCritical
方法之后,很容易返回一个*isCopy == false
的情况,因为它给Java虚拟机加了把锁,阻止了Jvm GC,从而直接指向Java虚拟机内存,该方法调用时也要注意,期间不能调用其它JNI函数,不然容易出现死锁,而且用的时候尽量时间要短,用完之后马上归还(调用ReleaseStringCritical
)。
举例三:对象数组传递
1 |
|
举例四:DirectBuffer
DirectBuffer是直接在物理内存上开辟了一块空间,所以对于Java虚拟机来说,可以直接读写它,对于Native层也可以直接读写它,这样的话,就不需要拷贝了,而且拷贝也是需要成本的。
如下代码所示:
在Jave层直接往ByteBuffer里面写了一串数值比如1 2 3 4 5 6,在Native层可以直接读,但是要注意字节序的问题。
1 |
|
1 |
|
三、题目结论
- 通过long类型传递底层对象指针给Java层
- 注意String的几组函数操作的区别与适用场景
- 注意对象数组较大时localRef超过上限的问题
- 注意使用DirectBuffer时字节序的问题
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!