初次遇见 native 是在 java.lang.String 源码中的一个 intern 方法:
public native String intern();
因为还是第一次遇到,所以就去搜了一些文章进行了解。下面就对一些 Native 关键字进行一些总结。
native 也即 JNI —— Java Native Interface(Java 本地接口)。凡是一种语言,都希望是纯的。比如解决某一个方案就单单使用同一个语言来实现。而 Java 却不然,Java 平台有个用户和本地 C 代码进行相互操作的 API,称为 Java Native Interface (Java 本地接口)。也就是说,相当于使用 Java 语言声明了一个方法,而这个方法的具体实现是在其他语言(如 C、C++等)中实现的,所以 Java 中编写的也就类似于一个接口,只是这个接口被称作本地接口。
Java 使用本地接口也是有原因的,因为 Java 的平台无关性,有优势当然也有牺牲,它的缺点就是不能使用 Java 代码直接对一些底层进行操作,但是对底层的操作又是一个语言必不可少的,于是 Java 就想到了间接去操作底层,而中间利用的就是操作系统。所以有些方法,Java 声明为了 native ,具体的实现是在 DLL 中,JVM 去进行真正的操作。
简单记忆:native 方法是 Java 中声明,由操作系统中具体方法实现。
网友见解:
Java 本地方法适用的情况:
为了更好的理解 Java 中调用 Native 方法,特来编写一个具体的小的测试。
以下所有文件都存于个人本地文件夹:C:\Users\Eric\Desktop\NativeTest。
在文件夹下创建一个 HelloNative.java
文件,里面包含着一个 native 的方法和加载库的方法 loadLibrary。代码如下:
public class HelloNative {
static {
// 注意加载库的名字为 HelloNative,需要与下文的生成文件保持一致
System.loadLibrary("HelloNative");
}
public static native void sayHello();
@SuppressWarnings("static-access")
public static void main(String[] args) {
new HelloNative().sayHello();
}
}
首先注意的是 native 方法,然后那个加载库的静态代码块在后面也起作用。native 关键字告诉编译器(其实是 JVM)调用的是该方法在外部定义,这里指的是 C。
在当前文件夹下使用 CMD 命令行编译 HelloNative.java
,如下。
如果当前类中没有 Native 方法,那么我们可以直接使用 java 命令直接运行,但是此时大家直接运行这个代码,会出现以下结果:
意思是虚拟机说不知道如何找到 sayHello。因为我们定义的 sayHello 方法为 native 类型,所以我们还需要再进行下文的操作步骤。
在当前文件目录下运行 javah,得到包含该方法的 C 声明头文件 。命令如下:
javah HelloNative # 生成 .h文件
得到的结果如下:
得到的 HelloNative.h
文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloNative */
#ifndef _Included_HelloNative
#define _Included_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloNative
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloNative_sayHello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
这个头文件中可以看见我们声明的 Java 本地化 sayHello
方法,对应 C 的声明:JNIEXPORT void JNICALL Java_HelloNative_sayHello(JNIEnv *, jclass);
,我们只要实现这个方法即可。
注意:头文件中 jni.h 这个文件,是在本地 JDK 目录下的 include 文件夹中,例如我的目录:
生成了头文件之后,我们再在当前文件夹下创建一个 HelloNative.c
文件,并简单地实现 HelloNative.h
文件中声明的 sayHello
方法,代码如下:
// 包含刚才生成的.h文件
#include "HelloNative.h"
#include
JNIEXPORT void JNICALL Java_HelloNative_sayHello(JNIEnv *env, jclass thisClass) {
printf("Hello, Native!!");
}
结果:
到了这一步,我们需要将上述两个文件 HelloNative.c
和 HelloNative.h
编译为动态链接库。
这里说明两种方法:分步编译或一次性编译形成动态链接库文件。
(1)一次性编译
在 Windows CMD 命令行里,使用如下命令:
gcc -m64 -Wl,--add-stdcall-alias -I"C:/Program Files/Java/jdk1.8.0_181/include" -I"C:/Program Files/Java/jdk1.8.0_181/include/win32" -shared -o HelloNative.dll HelloNative.c
注意:上述 JDK 为个人本地路径,需要根据个人情况进行修改。-m64
表示生成 dll
库是 64 位的,参数 -I
指定头文件路径上述命令运行后,我们会在目录文件夹下生成相应的动态链接库文件,如下:
如果使用的 Windows 上面没有 gcc,需要先下载压缩包然后配置一下环境变量即可使用(两分钟就搞定),压缩包及配置步骤,这里推荐一条博文:解决mingw-w64外网下载太慢问题,离线包安装配置过程讲解
(2)分步编译
为了演示分步编译,我们先把上面一次性编译生成的动态链接库文件 HelloNative.dll
给删除掉。然后再执行如下命令:
$ gcc -c -I"C:/Program Files/Java/jdk1.8.0_181/include" -I"C:/Program Files/Java/jdk1.8.0_181/include/win32" HelloNative.c HelloNative.h
结果如下:
在这里我们只需要先关注生成的 HelloNative.o
文件,然后我们执行第二步骤的命令,将该文件编译为 dll
文件:
$ gcc -Wl,--add-stdcall-alias -shared -o HelloNative.dll HelloNative.o
结果如下:
使用 2.4 中的任意一种方法生成动态链接库 HelloNative.dll
文件后,我们再次使用 java 命令运行 Java 类,结果如下:
我们可以看到 Java 类已经可以成功运行了,并且我们也可以看出它运行的实际是我们使用 C 语言编写的实现方法,它作为本地方法 native 来被 Java 代码调用。
可以将 native 方法比作 Java 程序同C程序的接口,其实现步骤:
JNI 调用 C 流程图:
参考文章