简介:JNI(Java Native Interface)是Java平台的一部分,允许Java代码与其他语言编写的代码进行交互。本文通过一个实例演示了如何用纯C语言通过JNI调用Java对象的方法。这包括设置开发环境、编写Java代码、生成JNI头文件、编写C代码以及编译和运行的完整流程。JNI允许本地代码访问JVM,并执行Java类的方法,创建和操作Java对象。尽管JNI在性能开销方面有成本,但在需要高性能或集成C/C++库时非常有用。开发者应谨慎处理内存管理和线程安全问题。
1. JNI定义和作用
Java Native Interface(JNI)是Java平台标准版的一部分,它为Java代码和其他语言编写的代码之间提供了一种交互机制。通过JNI,Java程序员可以调用由C或C++等语言编写的本地应用程序接口(API)。
1.1 JNI的功能性
JNI允许Java代码和本地代码进行交互,从而实现多种功能: - 调用第三方库的本地代码。 - 重用已经存在的本地代码库。 - 实现性能关键部分的代码,提高执行效率。
1.2 JNI的使用场景
在实际开发中,JNI的使用场景广泛,例如: - 图像处理和多媒体编解码。 - 硬件访问和系统级调用。 - 算法加速,如利用本地库中的优化算法。
在接下来的章节中,我们将详细探讨如何设置开发环境,编写Java和本地代码,以及如何将它们整合在一起,构建一个高效的JNI应用程序。
2. 开发环境设置
为了正确设置开发环境,需要了解JNI开发中常用的工具有哪些,以及如何配置这些工具以方便开发人员使用。本章将重点介绍cygwin的安装配置以及makefile的基本用法,这些都是进行JNI开发的基础。
2.1 cygwin环境的安装和配置
2.1.1 下载和安装cygwin
Cygwin 是一个为 Windows 提供 Unix-like 环境的软件包。它为开发人员提供了一个在 Windows 上运行 Unix 命令行工具的环境,这对于构建和编译基于 Unix 的应用程序特别重要。在进行 JNI 开发之前,我们需要在 Windows 系统上安装 Cygwin。
- 访问 Cygwin 官网下载安装程序(通常是一个名为
setup-x86_64.exe
或setup-x86.exe
的文件)。 - 执行下载的安装程序,并在安装向导的指导下,选择要安装的包。至少需要安装
gcc-core
和make
包,它们分别用于编译 C/C++ 代码和简化编译过程。 - 完成安装并打开 Cygwin 终端,我们可以开始编写和编译代码了。
# 示例命令,安装 gcc-core 和 make
./setup-x86_64.exe -P gcc-core -P make
2.1.2 配置cygwin环境变量
安装好 Cygwin 后,需要将其添加到系统的环境变量中,这样我们就可以在任何命令行窗口中运行 Cygwin 命令了。
- 在 Windows 系统中,搜索“环境变量”并选择“编辑系统环境变量”。
- 点击“环境变量”,在“系统变量”中找到并编辑“Path”变量。
- 将 Cygwin 安装目录的 bin 路径添加到 Path 中,例如:
C:\cygwin64\bin
(根据实际安装路径更改)。
# 示例,添加 Cygwin bin 路径到 Windows 系统环境变量
C:\cygwin64\bin
- 重启计算机或命令提示符,以使新的环境变量设置生效。
2.2 makefile的基本用法
2.2.1 makefile的语法结构
Makefile 是一种描述项目中文件如何编译和链接的文本文件,它告诉 make
程序如何编译和链接程序。Makefile 的基本语法结构包括目标(target)、依赖(dependencies)和命令(commands):
- 目标 :通常是编译后的产品,如可执行文件或库文件。
- 依赖 :是生成目标所需的文件列表。
- 命令 :是用于创建目标的 shell 命令,命令前必须有一个制表符。
一个基本的 makefile 文件示例如下:
all: my_program
my_program: main.o helper.o
gcc -o my_program main.o helper.o -lmylib
main.o: main.c
gcc -c main.c
helper.o: helper.c
gcc -c helper.c
2.2.2 使用makefile简化编译过程
Makefile 能够帮助开发者简化编译过程,尤其是对于包含多个源文件和库依赖的复杂项目。通过编写 makefile 文件,可以轻松管理项目中的编译规则,减少重复编译的需要。
例如,如果我们有一个名为 main.c
的源文件和一个名为 helper.c
的辅助文件,我们希望编译成一个名为 my_program
的可执行文件。一个简单的 makefile 可以这样写:
# 定义编译器
CC=gcc
# 定义编译选项
CFLAGS=-I/usr/include
# 定义链接选项
LDFLAGS=-L/usr/lib -lmylib
# 目标文件
TARGET=my_program
# 依赖文件
OBJS=main.o helper.o
# 最终目标
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)
在这个 makefile 中,我们定义了编译器和链接器选项,指明了依赖关系,并且通过 all
目标将它们关联起来。现在,每当我们执行 make
命令时,系统会根据文件的修改时间,自动决定需要重新编译哪些源文件,并生成最终的可执行文件。
# 使用 make 命令来编译程序
make
通过使用 makefile,可以大幅提升开发效率,让开发者更加专注于代码的编写,而不是繁琐的编译过程。
3. Java代码编写和本地方法定义
3.1 编写Java代码
3.1.1 创建Java类和方法
在开始编写本地方法之前,首先要创建一个Java类,并在其中定义需要调用本地方法的方法。在Java中,使用关键字 native
来声明本地方法。本地方法的实现将在C或C++代码中完成,但在Java类中,我们仅能提供一个方法的声明。
下面是一个示例Java类,其中包含了一个本地方法的声明:
public class NativeMethodDemo {
// 加载包含本地方法实现的本地库
static {
System.loadLibrary("nativeMethodDemo");
}
// 声明本地方法
public native void nativeMethod();
// 提供一个Java方法来调用本地方法
public void callNativeMethod() {
nativeMethod();
}
}
在上述代码中, System.loadLibrary("nativeMethodDemo")
用于加载一个名为 nativeMethodDemo
的本地库。这个库包含 nativeMethod()
方法的具体实现。该库文件名通常以 lib
开头,并以 so
结尾,在Linux系统中,或者以 dll
结尾,在Windows系统中。
3.1.2 使用关键字native声明本地方法
native
关键字的使用标志着Java虚拟机(JVM)将不再提供该方法的具体实现,而是需要依赖底层的本地代码来执行。本地方法的名称会由Java的JNI接口自动转换成一个特定的格式,使得在C或C++代码中能够正确识别并实现。
需要注意的是,本地方法不能有任何实现代码,即使是一对大括号 {}
也不允许。Java编译器会因为找不到对应的本地方法实现而报错,如:
public native void nativeMethod() {
// 这是不正确的,会导致编译错误
}
3.2 生成JNI头文件
3.2.1 使用javah命令生成头文件
一旦你定义了本地方法,接下来的步骤是生成一个头文件,这个头文件将用于本地方法的C或C++实现。你可以使用 javah
命令来生成这个头文件,或者在较新的JDK版本中使用 javac -h
命令。这个头文件将包含必要的本地方法声明和一些JNI数据类型的声明。
执行生成头文件的命令如下:
javah -jni NativeMethodDemo
或者,如果你使用的是更新的JDK:
javac -h . NativeMethodDemo.java
该命令在当前目录下生成一个名为 NativeMethodDemo.h
的头文件。该文件包含了用于实现本地方法的C/C++函数原型。
3.2.2 探讨头文件中函数声明的意义
头文件中声明的函数原型,为本地方法的实现提供了模板。例如,上节中声明的本地方法 nativeMethod()
在生成的头文件中可能会被定义成如下形式:
JNIEXPORT void JNICALL Java_NativeMethodDemo_nativeMethod(JNIEnv *, jobject);
这里的 JNIEXPORT
和 JNICALL
是特定于JNI的宏,分别对应于在平台相关的代码中导出或导入函数的约定。 JNIEnv *
是Java环境指针,提供了对JNI函数的访问,而 jobject
是指向Java对象本身的引用。这个函数原型告诉开发者,如何将Java端的调用映射到C/C++函数中。
理解了这个头文件中声明的函数原型,开发者就能够创建本地库文件,包含具体的本地方法实现代码,并使用Java与本地语言之间的桥接器完成复杂功能的实现。
4. C代码实现本地方法
4.1 编写C代码实现本地方法
4.1.1 函数命名规范和实现步骤
当Java代码中的本地方法使用 native
关键字声明后,需要开发者手动实现这些方法。这些方法通常在C或C++中编写,然后通过JNI接口供Java代码调用。在实现本地方法时,需要遵守特定的命名规则,这有助于Java虚拟机(JVM)识别和链接Java方法与对应的本地实现。
函数命名规范如下: - 函数名必须以 Java_
作为前缀,紧接着是完全限定的Java类名(包括点分隔符转换为下划线),最后是Java方法名和方法签名。 - 方法签名是JVM用来区分重载方法的。它由方法描述符组成,该描述符给出了方法的参数类型和返回值类型的编码。
例如,对于类 com.example.MyClass
中的本地方法 int add(int a, int b)
,其对应的C函数名将是 Java_com_example_MyClass_add
。
实现步骤详解
- 编写函数原型 :首先,根据命名规则确定C函数的原型。
- 包含必要的JNI头文件 :为了使用JNI函数接口,需要包含
jni.h
头文件。 - 实现本地方法 :在C源文件中编写方法实现,并使用JNI API与Java对象进行交互。
- 编译C代码 :编译C代码生成动态链接库(.dll在Windows上或.so在Linux上),供Java调用。
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_example_MyClass_greetNative(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello from C!");
}
在上述示例中, greetNative
方法实现了由Java声明的本地方法。其功能是在C中创建一个包含字符串"Hello from C!"的新Java字符串,并返回它。
4.1.2 理解和使用JNI函数接口
JNI提供了丰富的API来在本地代码中与Java对象进行交互。这些API主要分为如下几类: - 数据类型转换 :如 GetByteArrayElements
、 ReleaseByteArrayElements
等。 - 对象操作 :如 NewObject
、 GetObjectClass
、 GetFieldID
等。 - 异常处理 :如 ExceptionOccurred
、 ExceptionDescribe
、 ExceptionClear
等。 - 线程管理 :如 AttachCurrentThread
、 DetachCurrentThread
等。
这些函数允许开发者进行复杂的数据类型转换、操作Java对象、抛出和处理异常以及管理线程等操作。
示例代码逻辑分析
在上述 greetNative
函数中, NewStringUTF
函数用于创建一个包含UTF-8编码字符串的 jstring
对象。它接收一个 JNIEnv
指针和一个以null结尾的UTF-8编码的字符串。函数返回一个指向新创建的 jstring
对象的指针。
代码块参数说明和逻辑分析
JNIEXPORT jstring JNICALL
Java_com_example_MyClass_greetNative(JNIEnv *env, jobject obj) {
const char *c_str = "Hello from C!";
return (*env)->NewStringUTF(env, c_str);
}
在该代码段中: - JNIEXPORT
和 JNICALL
是用于JNI函数声明的宏,它们确保了函数的名称可以正确地被JVM解析。 - jstring
是JNI中定义的类型,用于表示Java中的字符串。 - JNIEnv
是一个指向结构体的指针,它包含了一组函数指针,这些函数指针可以被用来调用各种JNI提供的功能。 - jobject
是JNI中定义的类型,用于表示Java中的对象。 - NewStringUTF
函数创建一个Java字符串对象。
4.2 Java和C的数据类型转换
4.2.1 Java基本类型与C类型的映射关系
JNI定义了Java基本类型与C语言数据类型的映射关系。这确保了在Java虚拟机和本地代码之间传递参数和返回值时数据类型的正确转换和一致性。
| Java类型 | C语言类型 | |-----------|------------| | boolean | jboolean | | byte | jbyte | | char | jchar | | short | jshort | | int | jint | | long | jlong | | float | jfloat | | double | jdouble | | void | void |
这些类型前缀为 j
,如 jint
对应C语言中的 int
类型。JNI还提供了对应数组和类对象的类型,如 jintArray
对应Java中的 int[]
。
4.2.2 数组和字符串的传递处理
JNI提供了特别的API用于处理Java数组和字符串。由于Java和C语言在内存管理上的差异,JNI提供了如下函数来处理这些数据结构:
- 数组访问 :如
GetByteArrayRegion
、SetByteArrayRegion
等。 - 字符串访问 :如
GetStringUTFChars
、ReleaseStringUTFChars
等。
这些函数允许本地代码读取和写入Java数组以及操作Java字符串。它们在底层处理了内存和引用的分配与释放,确保了安全和高效的内存操作。
数组传递的代码示例
考虑Java中有一个 int[]
数组,要将其元素传到C中进行处理的示例代码:
JNIEXPORT void JNICALL
Java_com_example_MyClass_processArray(JNIEnv *env, jobject obj, jintArray array) {
// 获取Java数组的长度
int len = (*env)->GetArrayLength(env, array);
// 锁定数组数据以获取指向元素的指针
jboolean isCopy;
jint *arr = (*env)->GetIntArrayElements(env, array, &isCopy);
// 处理数组(这里仅作为示例,实际代码可能对数组进行操作)
for (int i = 0; i < len; ++i) {
arr[i] = arr[i] * 2;
}
// 释放本地引用并更新Java数组
(*env)->ReleaseIntArrayElements(env, array, arr, 0);
}
在上述代码中, GetIntArrayElements
函数返回了指向本地数组的指针 arr
。在处理完数组后,通过调用 ReleaseIntArrayElements
来释放本地引用,并可以选择性地将更改反映到原始Java数组中。 JNI_COMMIT
、 JNI_ABORT
和 0
是 ReleaseIntArrayElements
函数的模式参数,用于指示是否要同步修改回Java数组。
字符串传递处理
Java字符串需要使用 GetStringUTFChars
获取C中的字符指针,并通过 ReleaseStringUTFChars
来释放。例如:
JNIEXPORT void JNICALL
Java_com_example_MyClass_processString(JNIEnv *env, jobject obj, jstring jstr) {
const char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);
// 处理字符串
printf("C string: %s\n", c_str);
// 释放C字符串并释放Java字符串资源
(*env)->ReleaseStringUTFChars(env, jstr, c_str);
}
以上示例说明了如何获取Java字符串的C表示,并在处理完毕后释放相关资源。在JNI中,本地代码和Java代码之间进行数据传递时,需要对内存进行管理。遵循这些规则可以避免内存泄漏和其他潜在的内存错误。
5. 编译和运行动态链接库
在本章中,我们将深入探讨如何利用 makefile
来编译动态链接库,并且介绍如何运行Java程序以调用这些库。动态链接库(Dynamic Link Library,DLL)是操作系统在运行时加载和链接程序中的共享库。在JNI的上下文中,动态链接库通常用于承载用本地语言(如C或C++)编写的实现代码。本章将分步骤深入探讨这一过程。
5.1 利用makefile编译动态链接库
makefile
是Unix世界中常用的构建工具,它通过编写规则来管理程序的编译流程。在JNI项目中, makefile
可以帮助我们组织和自动化编译过程。
5.1.1 配置makefile以编译JNI代码
配置 makefile
需要我们定义编译规则和依赖关系。以下是一个简单的示例,展示如何配置 makefile
来编译包含本地方法的C代码。
# 定义编译器
CC=gcc
# 定义编译选项
CFLAGS=-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
# 定义目标库名
LIB=libnative-lib.so
# 默认目标
all: $(LIB)
# 编译本地代码并生成动态链接库
$(LIB): native.c
$(CC) $(CFLAGS) -shared native.c -o $@
# 清理编译产物
clean:
rm -f $(LIB)
# 设置JAVA_HOME环境变量(根据实际情况进行修改)
JAVA_HOME=/path/to/java/home
在这个 makefile
中,我们首先定义了编译器 CC
和编译选项 CFLAGS
,包括了必要的JDK头文件路径。然后定义了目标库文件名 LIB
。 all
目标会根据依赖关系调用 $(LIB)
规则,该规则会编译 native.c
文件并生成名为 libnative-lib.so
的动态链接库。
5.1.2 检查编译错误和调试技巧
在编译过程中,可能会遇到各种错误。以下是一些常见的错误类型和调试建议:
- 头文件找不到 :检查
CFLAGS
中的JAVA_HOME
路径是否正确,确保JDK的include目录被包含在内。 - 函数未定义 :确保所有使用的JNI函数都有对应的实现,并且链接时没有遗漏库。
- 编译器未找到 :确保
makefile
中的CC
变量设置正确,并且该编译器在你的系统路径中。
调试技巧: - 使用 make -n
命令来模拟构建过程,查看实际将要执行的命令。 - 使用 gdb
或其他调试工具调试生成的动态链接库。
5.2 运行Java程序调用动态链接库
一旦动态链接库被成功创建,下一步是让Java程序能够找到并加载这些库,然后调用其中的本地方法。
5.2.1 设置环境变量加载动态库
在Linux环境下,可以通过 LD_LIBRARY_PATH
环境变量指定动态链接库的路径,这样Java程序在运行时就能找到并加载库。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/library
在Windows环境下,则需要设置 PATH
环境变量:
set PATH=%PATH%;C:\path\to\library
5.2.2 编写测试代码验证JNI功能
编写测试代码来验证JNI功能是一个重要步骤。确保本地方法能够正确地被Java代码调用,并且实现预期的功能。
public class TestJNICode {
// 加载动态库
static {
System.loadLibrary("native-lib");
}
// 声明本地方法
public native String getSystemTime();
public static void main(String[] args) {
TestJNICode tjc = new TestJNICode();
// 调用本地方法并打印结果
System.out.println(tjc.getSystemTime());
}
}
在上述Java代码中,我们通过 System.loadLibrary
加载了动态链接库,并声明了一个本地方法 getSystemTime()
。然后在 main
方法中实例化 TestJNICode
类,并调用 getSystemTime
方法来打印系统时间。
编译并运行Java程序后,如果一切配置正确,应该能够看到从本地方法返回的系统时间信息。
javac TestJNICode.java
java TestJNICode
如果出现错误,可以使用 System.out.println
在Java和C代码中添加输出语句来进一步调试。
以上即为第五章“编译和运行动态链接库”的内容。通过本章的学习,读者应能够掌握利用 makefile
来编译动态链接库,并运行Java程序以调用这些库的基本方法。
6. 性能开销和适用场景讨论
JNI(Java Native Interface)是一个强大的工具,它允许Java代码和本地应用程序库进行交互,但这并不意味着它没有缺点。在本章中,我们将深入探讨JNI调用的性能开销,并讨论其适用场景,以及在实际开发中的优势与局限。
6.1 分析JNI调用的性能开销
6.1.1 对比JNI与纯Java性能差异
JNI调用涉及到Java虚拟机(JVM)与本地代码之间的交互,这本身就是一种额外的开销。为了对比JNI与纯Java代码的性能差异,我们可以设计一个简单的实验。假设我们有一个计算密集型的操作,比如矩阵乘法,我们分别用纯Java代码和通过JNI调用C实现的本地方法进行计算。然后我们可以使用JMH(Java Microbenchmark Harness)或其他性能测试工具来测量两者的执行时间。
在JMH基准测试中,我们可以看到JNI调用的开销主要来自于参数的传递、本地方法的调用、返回值的处理以及可能的资源管理。对比JNI和纯Java的基准测试结果,我们通常会发现JNI调用会有更高的开销,这是因为每一次JNI调用都需要进行上下文切换,而且数据的转换也可能耗时。
6.1.2 性能优化策略和建议
尽管JNI调用可能带来性能开销,但通过一些优化策略,我们可以最大限度地减少这些开销。首先,我们可以减少JNI方法的调用次数,将更多的操作放在本地代码中执行,以减少上下文切换的次数。其次,可以减少传递给本地方法的数据量,只传递必要的数据,而不是将整个对象传递到本地代码中。第三,使用缓存和预先分配资源来减少动态内存分配的次数,这可以减少因内存管理而产生的额外开销。最后,可以考虑使用更加高效的通信机制,比如使用共享内存或直接内存访问(DMA)。
6.2 探讨JNI的适用场景
6.2.1 JNI在实际开发中的优势和局限
JNI在实际开发中主要有两大优势:一是可以复用已有的本地库,无需重写大量代码,这对于大型项目尤其重要;二是可以访问操作系统底层功能和硬件,这使得JNI成为了连接Java应用与底层平台的强大桥梁。然而,JNI也有其局限性。例如,JNI代码的调试比纯Java代码更为复杂,开发和维护成本较高。此外,由于JNI可能带来安全风险,开发者在使用时需要格外小心。
6.2.2 案例分析:JNI在不同项目中的应用
我们可以通过分析几个具体的项目案例来进一步了解JNI的适用场景。例如,在Android开发中,许多图像处理库(如OpenCV)和游戏引擎(如Unity)都使用JNI来访问Java层提供的接口。在服务器端,JNI可以用来提升某些计算密集型任务的性能,比如加密和解密操作、高性能网络通信等。在桌面应用开发中,JNI被用来实现桌面环境的特定功能,例如使用Java编写用户界面,但使用本地代码来实现某些特定的用户交互,如复杂的图形渲染或者高精度定时器。
通过以上分析,我们可以看出,尽管JNI带来了性能开销,但在需要访问本地资源和库的场景下,JNI仍然是一个不可或缺的选择。开发者在使用JNI时,需要权衡其优势和局限,以及考虑项目的具体需求和上下文环境。
7. 总结与展望
7.1 回顾JNI中C调用Java方法的关键点
Java Native Interface (JNI) 提供了一种机制,允许Java代码和其他语言写的代码进行交互。在使用JNI时,一个非常重要的方面是C代码如何调用Java方法。以下是这一过程中的关键步骤和最佳实践的回顾。
7.1.1 重申关键步骤和最佳实践
- 查找Java类 :
- 在C代码中,首先需要获取Java虚拟机(JVM)实例的引用,然后才能进行进一步的操作。
- 使用
FindClass
函数来定位并获取Java类的引用。
jclass cls = env->FindClass("java/lang/String");
- 获取方法ID :
- 确定了要调用的Java方法后,需要使用
GetMethodID
或GetStaticMethodID
来获取该方法的ID。
- 确定了要调用的Java方法后,需要使用
jmethodID mid = env->GetMethodID(cls, "substring", "(I)Ljava/lang/String;");
- 调用Java方法 :
- 有了方法ID之后,就可以通过
Call<type>Method
系列函数来调用Java方法了。 - 注意方法调用的参数和返回值类型,这将决定使用哪个
Call<type>Method
函数。
- 有了方法ID之后,就可以通过
jstring result = (jstring)env->CallObjectMethod(javaObj, mid, 2);
- 异常处理 :
- 在JNI中调用Java方法时,需要小心处理可能出现的异常。
- 使用
ExceptionCheck
检查是否有未处理的异常,并使用ExceptionDescribe
打印异常信息。
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
- 清理和资源释放 :
- 在完成对Java对象的操作后,应适当地释放所有本地引用,以避免内存泄漏。
env->DeleteLocalRef(javaObj);
7.2 展望JNI技术的未来发展趋势
7.2.1 新兴技术对JNI的潜在影响
随着新兴技术的不断发展,JNI技术同样面临着变革的压力和机遇。例如,随着多语言运行时环境的流行,JNI不仅能够连接Java与C/C++,未来还可能支持更多语言的互操作性,例如Rust或Go。此外,随着云计算和容器技术的发展,JNI的应用场景将有可能扩展至分布式系统中,用于实现不同服务间的语言无关通信。
7.2.2 JNI在多语言编程环境中的角色
在多语言编程环境中,JNI的作用和地位可能会发生变化。传统的单语言应用正在向微服务架构转变,这意味着系统将由多种语言编写的独立服务组成。JNI作为一种能够桥接不同语言的技术,其在确保服务间通信效率和兼容性方面将扮演重要角色。但与此同时,开发者可能需要开发新的工具和最佳实践,以适应多语言环境带来的复杂性。
综上所述,JNI作为一个成熟的技术,依然在多语言编程环境中占有不可替代的位置。而随着技术的演进,我们有理由相信JNI技术将会不断适应新的发展,以满足未来软件开发的需求。
简介:JNI(Java Native Interface)是Java平台的一部分,允许Java代码与其他语言编写的代码进行交互。本文通过一个实例演示了如何用纯C语言通过JNI调用Java对象的方法。这包括设置开发环境、编写Java代码、生成JNI头文件、编写C代码以及编译和运行的完整流程。JNI允许本地代码访问JVM,并执行Java类的方法,创建和操作Java对象。尽管JNI在性能开销方面有成本,但在需要高性能或集成C/C++库时非常有用。开发者应谨慎处理内存管理和线程安全问题。