关键字native
native
1. 含义
在 JDK 源代码中,有一些方法是被关键字 native 修饰的,就比如 Object
类:
那 native
的作用是什么呢?
native
修饰的方法表示这个方法是原生函数,其具体实现由非 Java 语言实现,比如 C 语言,其代码定义不在 Java 类中,而是在外部文件中(一般是动态链接库 .dll 文件)。
native
修饰的方法称为本地方法,当在 Java 代码中调用该方法时,JVM 会到外部定义中寻找它的实现代码
在 Java 平台中,与本地代码进行互操作的 API,称为 Java Native Interface (Java 本地接口)。
2. native 方法实现
2.1 方法声明
要实现 native 方法,首先要声明一个 native 方法。
比如下面的这个类 HelloNative
,它声明了一个本地方法 sayHello
:
运行时,JVM 会寻找本地方法 sayHello
的外部定义实现(一般是 C/C++ 实现),如果 JVM 没有找到并加载进去,执行 sayHello
方法时就会出现和以下类似的错误信息:
1 | package com; |
错误信息日志:
1 | Exception in thread "main" java.lang.UnsatisfiedLinkError: com.demo.HelloNative.sayHello()V |
在这里,sayHello
方法并没有外部实现,找不到肯定会报错。
可以自己手写一个外部实现代码,并让 JVM 加载它,然后就可以正常调用 sayHello
了。
2.2 外部定义实现
Java 对于 JNI 接口方法定义格式有一定的要求,所以不是随便写就行的。
- 按照一般的 C/C++ 的文件格式可能无法正常加载到 JVM 中
- JDK 中提供了一个
javah
命令,可以用来生成符合 JNI 样式的标头文件
在类 HelloNative
所在包的“根目录”执行以下命令:
这样会在源文件 HelloNative.java
所在目录生成 JNI 标头文件 com_demo_HelloNative.h
。
javah
命令默认生成的标头文件名是“包名\_类名
”
生成的 com_demo_HelloNative.h
头文件的内容如下:
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
从头文件可以看到,javah
命令生成的标头文件是使用 C 语言声明定义的(extern "C"
):
- JNI 接口方法名称是“
Java\_包名\_类名\_方法名
”,该方法和 Java 中的 native 方法是一一对应的
有了头文件的接口方法声明,就可以开始实现具体的代码了。
创建一个文件 com_demo_HelloNative.c
,并引入上面生成的头文件 com_demo_HelloNative.h
:
1 | #include "com_demo_HelloNative.h" |
实现 native 方法后,还不能直接在 Java 中用,接下来还需要:
- 将头文件(.h)和实现文件(.c)结合生成动态链接库文件(DLL),然后才能在 Java
中使用
不同编译软件的生成命令可能不太一样,比如利用 MinGW GCC 的命令生成:
1 | # -m64 是指编译成64位的库 |
参数 -I
是用于指定头文件(.h,例如 jni.h
)的寻找路径。
编译完成后,就可以生成一个实现了 sayHello
接口的 HelloNative.dll
动态链接库文件了。
2.3 加载外部定义
生成 dll 后,还要把它加载到 JVM 中后才能使用,所以需要 JVM 能找到 dll 文件:
- dll 文件需要放到 JVM 能够找到的位置,它才能被正确加载
- JVM 默认的加载路径是程序当前目录,或者
System.getProperty("java.library.path")
所以只要把 dll 文件放到这些路径中的其中一个,JVM 就能够正常加载到 dll 文件。
加载好 dll 文件后,就可以调用 native 方法了:
1 | package com; |
程序的运行结果:
3. 流程图
1 | graph LR |
4. 总结
- native 方法不是由 java 语言实现的,而是由 C/C++ 语言实现的
- native 的外部定义实现一般是编译成 dll 文件后,再被 JVM 加载
- native 方法的主要作用是为了跨平台、速度快、隐藏敏感代码
- native 方法可以提供 java 访问操作系统底层的需求