JNI
Java Native interface,Java本地接口;是一种编程框架,使Java程序可以调用本地程序/库,也可以供本地程序调用。这里的本地程序一般指通过其他语言如C、C++、汇编等写成的,编译好的程序。是JVM规范的一部分,自JDK1.1开始就有了。为什么要使用JNI,主要有四个原因:
- Java天然需要JNI技术:Java是平台无关的,但JVM是平台相关的,对于调用平台API的功能,背后只能通过JNI技术在Native层分别调用不同平台的API。
- Java的运行效率不及C/C++:相比后者Java的运行效率要低一些,因此对于一些有密集计算需求的情况,会选择使用C/C++实现对硬件的操作,再通过JNI调用。
- Native层代码安全性高:反编译so的文件比反编译Class高。
- 复用代码:存在某些需要的功能已经用C/C++实现时,可以直接复用。
另外,总是看到native方法但不明白是什么,于是查了一下,native方法是通过关键字native声明的方法,表明该方法的实现不是使用Java编写的,而是用其他语言编写的(如C/C++),并通过JNI实现对该方法的调用。下面自己写代码练习一下。
实现一个native方法并使用java调用它
首先就是定义一个native方法先。
接下来,函数主体准备使用C++实现,接下来编译并生成C++的头文件。因为考虑到我使用的是java8且8u65,版本不算新,我一开始使用的是javah这个比较旧的方式来生成头文件,javac NativeDemo.java && javah NativeDemo
,但是一直提示“错误: 找不到 ‘NativeDemo’ 的类文件。”,后来查了一下说在包里面的要带上完整的包使用全路径类名,即javah sec.learn.NativeDemo
,但依然失败了。很奇怪,最后使用了javac这种被认为是更“现代”的方式解决了,javac -h . ./NativeDemo.java
。得到头文件sec_learn_NativeDemo.h
,内容如下:
|
|
JNI的函数命名是有要求的,如上需要是Java_完整类路径_函数名
,上面的JNIEnv *指对JNI环境的引用,像jcalss、jstring都是类型。JNI和Java定义的类型是需要转换的,不能直接使用java里的类型。类型对照如下图:
图源:https://www.javasec.org/javase/JNI/
使用VS Studio新建一个空项目,把所需的头文件jni.h
、jni_md.h
、sec_learn_NativeDemo.h
都通过添加现有项的方式,添加到项目中,但是要注意,仅仅将头文件包含进来还不够,还需要将头文件所在的路径包含在该工程的包含目录中,具体如下图所示:
编写代码如下,实现执行系统命令的功能:
|
|
点击“项目”==>“属性”==>“常规”==>“配置类型”,选择为“动态库(.dll)”,如下图所示:
点击最上方菜单栏的“生成”,选择“重新生成解决方案”,就会在解决方案的x64/Debug/
目录下生成动态链接库.dll文件,把它拷贝到我们的IDEA项目中(不拷贝也行,记住绝对路径就行)。接着使用System.load导入动态链接库文件,或者使用System.loadLibrary通过名称导入。不过使用后者需要该文件在java.library.path
中,后面我打印了一下,发现其实这个路径也将环境变量Path包含在内的。
接着就可以使用这个native的exec函数了,如下图所示:
也就是说借助JNI,可以通过编写native方法,不使用Java的API实现任何想要的功能。
文件系统
Java语言中对文件的任何操作最终都是通过JNI调用C语言的函数实现的。一开始我先学到文件系统,苦于看不懂JNI,于是先学习铺垫了JNI的知识;有了JNI的基础,再来看文件系统就好理解多了。
首先是Java中有两类文件系统,java.io
和java.nio
,后者的实现是sun.nio
。
图源:https://www.javasec.org/javase/FileSystem/FileSystem.html
Java.io 文件系统
java.io中抽象了一个java.io.FileSystem类出来,对于不同的操作系统有不同的实现。例如Windows和Unix下分别是java.io.WinNTFileSystem
和java.io.UnixFileSystem
。
图源:https://www.javasec.org/javase/FileSystem/FileSystem.html
java.io.FileSystem对文件的操作最终都通过调用动态链接库中C实现的native方法来实现,是一种基于阻塞模式的IO文件系统,属于Java早期的设计,在Java 7引入了NIO.2,是新设计的、更加现代化的,采用了非阻塞模式(提供异步IO的支持)的文件系统。
下面这段还看不明白,mark住
并不是所有的文件操作都在
java.io.FileSystem
中定义,文件的读取最终调用的是java.io.FileInputStream#read0、readBytes
、java.io.RandomAccessFile#read0、readBytes
,而写文件调用的是java.io.FileOutputStream#writeBytes
、java.io.RandomAccessFile#write0
。
NIO.2文件系统
为什么称之为NIO.2?
Java1.4曾引入了java.nio,nio意味(New Input/Output),Java 7等于对其做了革命性的重大更新,因此称之为NIO.2。同时与sun.nio
也是不同的,sun.nio
提供的是底层的支持,是Java的内部实现包,非公开API,并不能直接调用。
NIO在不同的操作系统上最终实现的类也是不一样的,例如Mac的实现类是: sun.nio.fs.UnixNativeDispatcher
,而Windows的实现类是sun.nio.fs.WindowsNativeDispatcher
。
纸上得来终觉浅,文件系统这块有机会还是多实践才能更了解
插曲-IDEA切换Java版本
添加了一个Java8的jdk,但是切换起来没有那么顺利,至少有三个地方需要改。
- 项目结构==>项目设置==>项目==>SDK和语言级别
- 项目结构==>项目设置==>模块==>源==>语言级别
- 设置==>构建、执行、部署==>编译器==>Java编译器==>目标字节码版本
- 如果使用了maven,且maven中有java.version的话,也需要更改(我没有遇到)
参考文献
https://zh.wikipedia.org/wiki/Java本地接口
https://zhaoshuming.github.io/2020/01/02/android-ndk-jni/
https://www.javasec.org/javase/JNI/
https://cloud.tencent.com/developer/article/2123817
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html