JNI的介绍举例以及内存管理

为了对Java能力的增强,Java最开始被研发就想到了设计Native接口,最开始是有了Native的接口定义,直接把Java中的对象内存给Native,后来加入了JNI,后来就删除了Native。Native接口还实现了Java的其他分支,如Visual J++实现RNI,GCJ实现的CNI。

JNI(Java Native Interface)是Java提供的一种机制,用于在Java虚拟机中调用本地代码(通常是C或C++代码),或者让本地代码调用Java类和方法。JNI提供了一组标准接口和约定,使Java应用程序可以与本地代码进行交互,从而实现更高效、更灵活和更强大的功能。

JNI的主要作用是提高Java应用程序的性能和功能。通过JNI,Java应用程序可以调用本地代码来执行一些高性能的任务,例如图像处理、音频处理、数据加密等等。此外,JNI还可以让Java应用程序与其他编程语言进行交互,例如C、C++、Python、Perl等等,从而扩展Java应用程序的功能和灵活性。

在JNI中,Java代码和本地代码之间的交互是通过Java本机方法(Native Method)来实现的。Java本机方法是一个由native关键字修饰的Java方法,它的实现在本地代码中完成。在Java应用程序中调用Java本机方法时,Java虚拟机将自动加载本地代码,并在本地代码中执行该方法。本地代码可以使用JNI接口来访问Java对象、调用Java方法、抛出Java异常等等。

以下是一个简单的JNI示例,演示如何在Java应用程序中调用本地代码:

  1. 定义Java本机方法

首先,我们需要在Java代码中定义一个本机方法,以指示Java虚拟机调用本地代码。本机方法的定义需要使用native关键字,例如:

java 复制代码
public class NativeDemo {
    public native void sayHello();
}

在这个示例中,我们定义了一个NativeDemo类,其中包含一个本机方法sayHello()。这个方法没有实现,而是在本地代码中实现。

  1. 生成本地代码

接下来,我们需要使用Java的工具链来生成本地代码。我们可以使用javac编译Java代码,然后使用javah工具生成本机代码的头文件。头文件将包含Java本机方法的声明和一些JNI接口的定义。例如:

java 复制代码
$ javac NativeDemo.java
$ javah -jni NativeDemo

生成的头文件NativeDemo.h的内容如下:

cpp 复制代码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeDemo */

#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     NativeDemo
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_NativeDemo_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. 实现本地代码

接下来,我们需要实现本地代码。在本例中,我们将使用C++来编写本地代码,实现sayHello()方法。我们需要包含jni.h头文件,并实现JNI接口中定义的函数。例如:

cpp 复制代码
#include <jni.h>
#include <iostream>
using namespace std;

JNIEXPORT void JNICALL Java_NativeDemo_sayHello(JNIEnv *env, jobject obj) {
    cout << "Hello from C++!" << endl;
}

在这个示例中,我们包含了jni.h头文件,并实现了Java_NativeDemo_sayHello()函数。该函数将输出一条"Hello from C++!"的消息。

  1. 编译本地代码

接下来,我们需要编译本地代码。在这个示例中,我们将使用g++编译本地代码,生成动态链接库(.so或.dll文件)。例如:

cpp 复制代码
$ g++ -shared -fPIC -o libnative.so NativeDemo.cpp -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux

在这个示例中,我们使用g++编译本地代码NativeDemo.cpp,并生成动态链接库libnative.so。我们还需要使用-I选项指定JNI头文件的路径。

  1. 运行Java应用程序

最后,我们可以运行Java应用程序,并调用本机方法。在Java应用程序中,我们需要使用System.loadLibrary()方法加载本地库。在本例中,我们需要加载libnative.so库。然后,我们可以创建NativeDemo对象,并调用其sayHello()方法。Java虚拟机将自动加载本地库,并在本地代码中执行该方法。例如:

java 复制代码
public class Main {
    public static void main(String[] args) {
        System.loadLibrary("native");
        NativeDemo demo = new NativeDemo();
        demo.sayHello();
    }
}

在这个示例中,我们使用System.loadLibrary()方法加载本地库native,然后创建NativeDemo对象,并调用其sayHello()方法。运行Java应用程序时,我们应该能够看到输出一条"Hello from C++!"的消息。

总之,JNI提供了一种机制,使Java应用程序能够调用本地代码,或让本地代码调用Java类和方法。通过JNI,Java应用程序可以实现更高效、更灵活和更强大的功能。实际上,JNI还涉及到一些复杂的问题,例如内存管理、线程同步、异常处理等等,需要仔细设计和实现。在实际应用中,需要根据具体情况进行调整和优化。

Native Method声明

Native Method除了增加Native声明外,和Java Method差不多。

so的加载

在Java中使用JNI(Java Native Interface)调用本地代码时,需要将本地代码编译成动态链接库(.so或.dll文件),并将其加载到Java虚拟机中。本地库的加载通常在静态代码块中完成,例如:

java 复制代码
```java
public class NativeLibrary {
    static {
        System.loadLibrary("myNativeLibrary");
    }
}
```

在这个示例中,我们使用`System.loadLibrary()`方法加载本地库`myNativeLibrary`。在静态代码块中调用`System.loadLibrary()`方法可以确保本地库在Java类加载时被加载到内存中。

`System.loadLibrary()`方法的参数是本地库的名称,不包括文件扩展名。在加载本地库时,Java虚拟机将按照特定的搜索规则查找本地库。具体来说,Java虚拟机将按照以下顺序搜索本地库:

  1. 在JVM内置的路径下搜索。这些路径包括系统路径和扩展路径。

  2. 在Java库路径中搜索。Java库路径是通过`java.library.path`系统属性指定的。

  3. 在操作系统默认的路径中搜索。操作系统默认的路径通常包括系统目录、用户目录等。

如果在上述路径中找不到指定的本地库,则会抛出`UnsatisfiedLinkError`异常。

在实际应用中,需要根据具体情况进行本地库的加载。如果本地库位于非标准路径下,可以使用`System.setProperty()`方法设置`java.library.path`系统属性,以修改Java库路径。例如:

java 复制代码
```java
public class NativeLibrary {
    static {
        System.setProperty("java.library.path", "/path/to/my/library");
        System.loadLibrary("myNativeLibrary");
    }
}
```

在这个示例中,我们将`java.library.path`系统属性设置为本地库所在的路径,然后再调用`System.loadLibrary()`方法加载本地库。

总之,在Java中使用JNI调用本地代码时,需要将本地代码编译成动态链接库,并将其加载到Java虚拟机中。本地库的加载通常在静态代码块中完成,可以使用`System.loadLibrary()`方法加载本地库。在实际应用中,需要根据具体情况进行调整和优化。

Native注册方式分为静态注册和动态注册

  1. 静态注册

静态注册是将本机函数的声明和实现直接写在C或C++源文件中,并使用JNI提供的`JNIEXPORT`和`JNICALL`宏进行声明和实现。例如:

cpp 复制代码
```c
#include <jni.h>

JNIEXPORT void JNICALL Java_MyClass_myNativeMethod(JNIEnv *env, jobject obj) {
    // 实现本机函数
}
```

在这个示例中,我们在C源文件中实现了一个名为`Java_MyClass_myNativeMethod`的本机函数,并使用JNI提供的`JNIEXPORT`和`JNICALL`宏进行声明和实现。`JNIEXPORT`和`JNICALL`宏用于指定函数的调用约定,以确保函数能够被正确地调用。

静态注册的优点是简单易用,不需要编写额外的代码。但是,它的缺点是不够灵活,无法在运行时动态注册本机函数。

  1. 动态注册

动态注册是在C或C++源文件中编写一个名为`JNI_OnLoad`的函数,并在该函数中动态注册本机函数。`JNI_OnLoad`函数在本地库加载时自动调用,可以在其中注册本机函数。例如:

cpp 复制代码
```c
#include <jni.h>

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    jclass cls = (*env)->FindClass(env, "MyClass");
    if (cls == NULL) {
        return JNI_ERR;
    }

    JNINativeMethod methods[] = {
        {"myNativeMethod", "()V", (void *)&Java_MyClass_myNativeMethod},
    };

    if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}
```

在这个示例中,我们在`JNI_OnLoad`函数中动态注册了一个名为`myNativeMethod`的本机函数。首先,我们通过`GetEnv`函数获取JNIEnv指针。然后,我们使用`FindClass`函数获取Java类的引用,再使用`RegisterNatives`函数注册本机函数。`RegisterNatives`函数的参数包括Java类的引用、本机函数的信息以及本机函数的数量。在这个示例中,我们只注册了一个本机函数。

动态注册的优点是灵活,可以在运行时动态注册本机函数。但是,它的缺点是比较繁琐,需要编写额外的代码。

在Java中使用JNI调用本地代码时,需要将本地代码编译成动态链接库,并将其加载到Java虚拟机中。在本地代码中,需要将本机函数注册到JNI接口中,以便在Java代码中调用。本机函数的注册方式有两种:静态注册和动态注册。静态注册是将本机函数的声明和实现直接写在C或C++源文件中,并使用JNI提供的宏进行声明和实现。动态注册是在`JNI_OnLoad`函数中动态注册本机函数。静态注册简单易用,但不够灵活;动态注册比较繁琐,但灵活性更高。

JNI内存管理分为Java内存管理和Native内存管理,而Java的内存管理分为局部引用和全局引用。

局部引用是Native函数执行的时候持有Java对象,Native函数返回后,Java对象被虚拟机释放掉,Native函数参数里的对象都是局部引用,除了特殊函数,通过JNIEnv调用的函数返回的对象也是局部引用,JNI函数返回时会自动释放。

全局引用是Native手动持有和释放Java对象,GC不会处理全局引用的对象,被持有的对象只能同骨Native手动释放,全局一弄还有一种类型手弱全局引用,对象随时会被释放,使用前需要检查对象是否为nullptr,并使用前转换弱全局引用为局部引用,防止JNI执行过程中对象被GC释放掉。

至于Native的内存管理,我不会。

相关推荐
BillKu9 分钟前
Java + Spring Boot + Mybatis 实现批量插入
java·spring boot·mybatis
YuTaoShao10 分钟前
Java八股文——集合「Map篇」
java
dog shit1 小时前
web第十次课后作业--Mybatis的增删改查
android·前端·mybatis
有梦想的攻城狮2 小时前
maven中的maven-antrun-plugin插件详解
java·maven·插件·antrun
科技道人2 小时前
Android15 launcher3
android·launcher3·android15·hotseat
多吃蔬菜!!!3 小时前
排序算法C语言实现
数据结构
零叹3 小时前
篇章六 数据结构——链表(二)
数据结构·链表·linkedlist
硅的褶皱6 小时前
对比分析LinkedBlockingQueue和SynchronousQueue
java·并发编程
MoFe16 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
季鸢6 小时前
Java设计模式之观察者模式详解
java·观察者模式·设计模式