在看 JDK 源码的时候,不停往方法调用链下走,会发现到了很多标注了 native 的方法的地方就停止了。
native 标注的方法是 Java 本地方法,这些方法可以由 C、C++实现。当程序无法完全用 Java 实现的时候,本地方法可以处理这种情况。
一个简单的例子,在 java 中调 C++ 程序实现两个整数的求和。简单起见,在 Linux 平台实现。
创建一个 Java 程序
public class CalSum {
static {
// 这里写绝对路径,相对路径报错,未解决
System.load("/home/user/documents/java/sum/libcalsum_c.so");
}
// 声明 native 方法
public native static int calSum(int a, int b);
public static void main(String[] args) {
int a = 10;
int b = 20;
// 调用 C++ 实现的 native 方法求和
int sum = calSum(a, b);
System.out.println(a + " + " + b + " = " + sum);
}
}
编译 CalSum.java 并生成 CalSum.h 头文件
javac CalSum.java
javah CalSum
执行完 javah 命令后,会在当前目录下生成一个 CalSum.h 头文件,文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CalSum */
#ifndef _Included_CalSum
#define _Included_CalSum
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: CalSum
* Method: calSum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_CalSum_calSum
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
在当前目录下创建 CalSum.c 文件并用 C++ 实现求和功能
#include<jni.h>
#include "CalSum.h"
JNIEXPORT jint JNICALL Java_CalSum_calSum(JNIEnv *env, jclass object, jint a, jint b) {
jint sum = a + b;
return sum;
}
生成 libcalsum_c.so
gcc CalSum.c CalSum.h -I /opt/openjdk-8u262/include -I /opt/openjdk-8u262/include/linux -fPIC -shared -o libcalsum_c.so
:warning: 这里主要是注意需要在 jdk 安装路径下找到 jni.h 和 jni_md.h 两个头文件的路径,然后添加编译命令中。
运行 CalSum 程序得到 C++ 程序返回的结果
java CalSum
输出:
❯ java CalSum
10 + 20 = 30
所有流程总结
- 创建带有 native 标识的方法,并从 Java 中调用它
- Java 编译器生成 .class 字节码文件
- C/C++ 生成链接库
- 运行程序,执行字节码
- 执行到loadLibary或load调用的时候,添加一个 .so文件到这个进程中
- 执行到native方法的时候,通过方法签名,在已打开的.so文件中进行搜索
- 如果链接库内有对应方法,就会被执行,否则程序崩溃
参考: