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的内存管理,我不会。

相关推荐
一直学习永不止步2 分钟前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
尘浮生5 分钟前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料13 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
Winston Wood27 分钟前
Android Parcelable和Serializable的区别与联系
android·序列化
雷神乐乐29 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
清风徐来辽32 分钟前
Android 项目模型配置管理
android
小刘|34 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
JavaNice哥40 分钟前
1初识别jvm
jvm
涛粒子40 分钟前
JVM垃圾回收详解
jvm