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 实现步骤

  1. 在Java中声明native方法
  2. 使用javah生成.h头文件
  3. 编写对应的C/C++实现
  4. 编译为本地共享库(.so/.dll)
  5. 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加载过程分析

  1. System.loadLibrary调用Runtime.getRuntime().loadLibrary0()
  2. loadLibrary0调用ClassLoader.loadLibrary()
  3. 使用AppClassLoader从特定位置加载本地库文件

1.4 JNI利用场景

  1. 将自写dll和恶意war包绑定,通过任意文件上传漏洞部署
  2. 上传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加载过程

  1. Native.loadLibrary创建库处理程序handler
  2. 创建动态代理对象实现接口方法
  3. 缓存Native库选项到libraries Map
  4. 通过反射调用相应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实现原理

  1. 创建PtyProcessBuilder对象
  2. start()方法创建PTY进程选项
  3. WinPtyProcess提取配置选项
  4. 加载临时文件夹中的winpty.dll
  5. 调用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. 安全注意事项

  1. 本地代码执行具有与Java程序相同的权限
  2. 不当的本地代码可能导致内存泄漏或崩溃
  3. 加载不受信任的本地库存在安全风险
  4. 在沙箱环境中可能无法使用这些技术

7. 总结

JNI、JNA和JNR都提供了Java与本地代码交互的能力,各有优缺点:

  • JNI功能最强大但实现最复杂
  • JNA简化了JNI的使用但性能稍低
  • JNR在性能和易用性之间取得平衡

选择哪种技术取决于具体需求:需要最高性能选择JNI,追求开发效率选择JNA或JNR。

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方法 2. 生成.h头文件 生成的头文件内容: 3. 编写C实现 4. 编译为共享库 Windows: Linux: 5. Java调用 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实现示例 2.3 JNA加载过程 Native.loadLibrary 创建库处理程序handler 创建动态代理对象实现接口方法 缓存Native库选项到libraries Map 通过反射调用相应Native方法 3. 基于JNA的pty4j 3.1 pty4j介绍 pty4j是一个Java库,提供API启动子进程并通过标准I/O流交互,支持: 启动子进程并交互 设置超时时间 绑定输出处理器 获取子进程退出状态 3.2 pty4j命令执行示例 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。