JNI入门之HelloWorld
本文通过编写一个简单的 HelloWorld 带你熟悉下 JNI,包括如何编写一个 JNI 方法,如何打包成动态链接库,如何加载并调用等。
通常学习一项技术的第一步是从 HelloWorld 开始的,在开始前有必要先了解下什么是 JNI。
什么是 JNI
JNI 全称是 Java Native Interface,即 Java 本地编程接口。根据 JNI 规范,java 程序可以调用使用其他编程语言(例如C、C++、汇编语言等)编写的程序或类库。并且它是一个通用的 java 程序与本地方法交互的规范,对于符合要求的本地(Native)应用程序或类库,可在支持该规范的不同 java 虚拟机上正常运行。
编写 JNI 版 HelloWorld
下面就来写一个 JNI 版的 HelloWorld。主要的流程为:
- 创建 java 类,声明 native 方法
- 生成 native 方法的头文件
- 编写 native 方法的实现,生成动态链接库文件
- 在 java 中加载动态链接库并调用 native 方法
声明 native 方法
创建类 HelloWorldNative
,声明 native 方法 void sayHello()
1 | public class HelloWorldNative { |
生成头文件
有了 java 类里的 native 方法声明后,就可以使用 jdk 提供的工具生成 c 语言的头文件。
执行
javac HelloWorldNative.java
命令, 编译 java 类,生成字节码文件1
2
3➜ javac HelloWorldNative.java
➜ ls
HelloWorldNative.class HelloWorldNative.java执行
javah HelloWorldNative
命令,生成头文件HelloWorldNative.h
。javah
是 jdk 自带的命令,可用于根据 java class 文件生成 c 语言的头文件(header),详细介绍可参考命令的 man page。1
2
3➜ javah HelloWorldNative
➜ ls
HelloWorldNative.class HelloWorldNative.h HelloWorldNative.java生成的头文件内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class HelloWorldNative */
extern "C" {
/*
* Class: HelloWorldNative
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorldNative_sayHello
(JNIEnv *, jobject);
}对比下 java 里方法的定义为
void sayHello()
,转换后为void JNICALL Java_HelloWorldNative_sayHello(JNIEnv *, jobject)
,其转换规则如下- 前缀为
Java_
- 完全限定的类名(包括包名和类的全路径),中间以
_
分割 - 方法名
- 对于重载的 native 方法,方法名后要再跟上
__
和参数标签
- 前缀为
编写 native 方法
因为是 JNI 方法,所以需要引入 jni.h
,然后还要引入刚生成的 HelloWorld.h
。为了在控制台打印 HelloWorld
,我们再引入 stdio.h
。最后是将头文件 HelloWorldNative.h
里的方法声明复制过来,加上实现。注意方法的入参需手动加上名字,否则会报 error: parameter name omitted
错误。
1 |
|
编译生成库文件
这里使用的是 mac os 系统,jdk 安装在 /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk
目录下。使用 gcc 来编译 HelloWorldNative.c
并生成库文件,用到的参数如下:
指定 JNI 相关的头文件路径
目的是告诉 gcc 编译器去哪里查找 JNI 相关的
jni.h
等头文件。使用-I
参数指定路径1
2-I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include
-I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include/darwin指定生成动态链接库文件
使用
-shared
参数指定生成的是动态链接库,使用-o
参数指定生成的动态链接库文件名1
-shared -o libhello.so
最终执行的命令为
1 | gcc -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/include/darwin HelloWorldNative.c -shared -o libhello.so |
加载并调用 JNI 方法
生成的动态链接库要加载后才能使用,这里使用 System.load()
方法从绝对路径下加载到 JVM 中,然后就可以使用其提供的方法。相关代码如下:
1 | public class Test { |
编译成字节码
1
javac Test.java
执行
1
java Test
输出结果如下
1
2➜ java Test
HelloWorld!可使用
-verbose:jni
参数查看使用的 native 方法。可以看到动态链接了 native 方法HelloWorldNative.sayHello
。1
2
3
4
5
6
7
8➜ java -verbose:jni Test
[Dynamic-linking native method java.lang.Object.registerNatives ... JNI]
[Registering JNI native method java.lang.Object.hashCode]
[Registering JNI native method java.lang.Object.wait]
[Registering JNI native method java.lang.Object.notify]
...
[Dynamic-linking native method HelloWorldNative.sayHello ... JNI]
HelloWorld!
上面用到的所有文件如下,至此我们已经完成了 JNI 版 HelloWorld 的编写和调用。
1 | ├── HelloWorldNative.c |
参考
- 2019-10-13
jdk 中有很多 native 方法,比如 Object 类的 registerNatives 方法、String 类的 intern 方法等。这些方法在 java 层面只有接口定义,具体的方法实现则是在 jdk 中,采用 c/c++ 实现。本文主要讲下如何找到 native 方法的实现。
- 2019-11-12
javaagent 可以在 main 方法执行前执行一些操作,比如增加一些特殊启动逻辑、在类完成加载前对类的字节码进行修改等。本文不谈底层原理,只介绍如何从零手写一个javaagent。
- 2019-09-30
本文主要介绍下 JNI 相关的概念和一些原理,以便对 JNI 有一个整体认识。
- 2021-01-30
Mac 下编译 netty 报错,提示
Netty/Transport/Native/Unix/Common
模块编译失败,到网上搜索一下,并未发现有人遇到过类似问题,因此做下记录。 - 2017-10-15
Java 虚拟机所管理的内存包括多个运行时数据区域,每个区都有自己的特点。