JNI、JNA、JNR的浅入浅出
字数 1762 2025-08-06 12:20:54
Java本地接口技术详解:JNI、JNA与JNR
1. JNI (Java Native Interface)
1.1 JNI介绍
JNI(Java Native Interface)是Java提供的一种机制,用于在JVM上运行的应用程序与本地操作系统或其他本地库进行交互。通过JNI可以实现:
- Java应用程序调用本地代码(C/C++)
- Java与本地代码的无缝集成
- 访问操作系统底层接口(文件系统、网络等)
- 调用本地语言编写的库(图形界面库、数学计算库等)
1.2 JNI实现过程
1.2.1 实现步骤
- 在Java中声明native方法
- 使用javah生成.h头文件
- 编写对应的C/C++实现
- 编译为本地共享库(.so/.dll)
- Java程序加载并调用本地库
1.2.2 具体实现示例
1. 声明native方法
package com.company;
public class Command {
public native String exec(String cmd);
}
2. 生成.h头文件
javah -cp . com.company.Command
生成的头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_company_Command */
#ifndef _Included_com_company_Command
#define _Included_com_company_Command
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_company_Command
* Method: exec
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_company_Command_exec
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
3. 编写C实现
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <iostream>
#include "com_company_Command.h"
using namespace std;
JNIEXPORT jstring JNICALL Java_com_company_Command_exec
(JNIEnv *env, jobject obj, jstring cmd) {
const char *cmdStr = env->GetStringUTFChars(cmd, NULL);
FILE *fp;
char buffer[256];
fp = popen(cmdStr, "r");
std::string result = "";
while (fgets(buffer, sizeof(buffer), fp)) {
result += buffer;
}
pclose(fp);
env->ReleaseStringUTFChars(cmd, cmdStr);
return env->NewStringUTF(result.c_str());
}
4. 编译为共享库
Windows:
g++ -I "%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o cmd.dll Command.c
Linux:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o cmd.so Command.cpp
5. Java调用
package com.company;
public class Main {
public static void main(String[] args) {
System.loadLibrary("cmd");
Command command = new Command();
String ipconfig = command.exec("ipconfig");
System.out.println(ipconfig);
}
}
1.3 JNI加载过程分析
System.loadLibrary调用Runtime.getRuntime().loadLibrary0()loadLibrary0调用ClassLoader.loadLibrary()- 使用
AppClassLoader从特定位置加载本地库文件
1.4 JNI利用场景
- 将自写dll和恶意war包绑定,通过任意文件上传漏洞部署
- 上传jsp或注入class,将dll文件十六进制编码释放并调用
2. JNA (Java Native Access)
2.1 JNA介绍
JNA(Java Native Access)是简化Java调用本地库的工具,相比JNI:
- 提供易于使用的Java API
- 自动映射Java方法到本地库函数
- 无需手动编写映射代码
2.2 JNA实现示例
import com.sun.jna.Library;
import com.sun.jna.Native;
public class CommandExecutor {
public interface CLibrary extends Library {
// windows下的共享库
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("kernel32", CLibrary.class);
int WinExec(String command);
// linux下的共享库
// CLibrary INSTANCE = (CLibrary) Native.load("libc.so.6", CLibrary.class);
// int system(String command);
}
public static void main(String[] args) {
String command = "calc";
int returnValue = CLibrary.INSTANCE.WinExec(command);
System.out.println("Return Value: " + returnValue);
}
}
2.3 JNA加载过程
Native.loadLibrary创建库处理程序handler- 创建动态代理对象实现接口方法
- 缓存Native库选项到libraries Map
- 通过反射调用相应Native方法
3. 基于JNA的pty4j
3.1 pty4j介绍
pty4j是一个Java库,提供API启动子进程并通过标准I/O流交互,支持:
- 启动子进程并交互
- 设置超时时间
- 绑定输出处理器
- 获取子进程退出状态
3.2 pty4j命令执行示例
import com.pty4j.PtyProcess;
import com.pty4j.PtyProcessBuilder;
import java.io.*;
public class ptyTest {
public static void main(String[] args) throws IOException {
PtyProcessBuilder builder = new PtyProcessBuilder(
new String[]{"cmd.exe", "/c", "calc.exe"});
// 或Linux: new String[]{"/bin/bash","-c","ifconfig"}
PtyProcess process = builder.start();
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null){
System.out.println(line);
}
try {
process.waitFor();
process.destroy();
} catch (InterruptedException e) {
e.printStackTrace();
}
process.destroy();
}
}
3.3 pty4j实现原理
- 创建
PtyProcessBuilder对象 start()方法创建PTY进程选项WinPtyProcess提取配置选项- 加载临时文件夹中的
winpty.dll - 调用
winpty_spawn方法执行命令
4. JNR (Java Native Runtime)
4.1 JNR介绍
JNR是另一种Java调用本地库的技术,相比JNA:
- 采用更快、更轻量级的实现
- 使用字节码生成技术
- 运行时动态生成本地方法映射代码
- 避免JNI调用过程中的不必要开销
4.2 JNR特点
- 重写
jnr.ffi.Library.loadLibrary方法 - 加载Windows的
kernel32库 - 利用
CreateProcess函数创建命令管道和进程
5. 技术对比
| 特性 | JNI | JNA | JNR |
|---|---|---|---|
| 实现复杂度 | 高 | 中 | 低 |
| 性能 | 高 | 中 | 高 |
| 需要编写本地代码 | 是 | 否 | 否 |
| 自动类型映射 | 否 | 是 | 是 |
| 内存管理 | 手动 | 自动 | 自动 |
| 学习曲线 | 陡峭 | 平缓 | 平缓 |
6. 安全注意事项
- 本地代码执行具有与Java程序相同的权限
- 不当的本地代码可能导致内存泄漏或崩溃
- 加载不受信任的本地库存在安全风险
- 在沙箱环境中可能无法使用这些技术
7. 总结
JNI、JNA和JNR都提供了Java与本地代码交互的能力,各有优缺点:
- JNI功能最强大但实现最复杂
- JNA简化了JNI的使用但性能稍低
- JNR在性能和易用性之间取得平衡
选择哪种技术取决于具体需求:需要最高性能选择JNI,追求开发效率选择JNA或JNR。