Java本地方法

在看 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.hjni_md.h 两个头文件的路径,然后添加编译命令中。

运行 CalSum 程序得到 C++ 程序返回的结果

java CalSum

输出:

❯ java CalSum
10 + 20 = 30

所有流程总结

  1. 创建带有 native 标识的方法,并从 Java 中调用它
  2. Java 编译器生成 .class 字节码文件
  3. C/C++ 生成链接库
  4. 运行程序,执行字节码
  5. 执行到loadLibary或load调用的时候,添加一个 .so文件到这个进程中
  6. 执行到native方法的时候,通过方法签名,在已打开的.so文件中进行搜索
  7. 如果链接库内有对应方法,就会被执行,否则程序崩溃

参考:

详解 JNI(Java Native Interface)(一)

详解 JNI(Java Native Interface)(二)