JNI

JNI即Java Native Interface,它能在Java层实现对本地方法的调用,一般本地的实现语言主要是C/C++,其实从虚拟机层面来看JNI挺好理解,JVM主要使用C/C++ 和少量汇编编写,在执行Java字节码时如果遇到有某个方法标明为Native的则从JVM中找到对应的C/C++函数,一般本地方法对应的函数会被注册到JVM中。

使用JNI能让Java与本地语言交互,但一般也意味着丧失了跨平台性,而有些场合会使用。比如标准的Java特性不符合你的需求时,比如在性能要求很高的某段逻辑。

从一个例子说起

  • 编写一个Java类提供本地加密的方法,其中加密方法为本地方法,实现是在ByteCodeEncryptor动态库。
package com.seaboat.bytecode;

public class ByteCodeEncryptor {
  static{
    System.loadLibrary("ByteCodeEncryptor");
  }

  public native static byte[] encrypt(byte[] text);

}
  • 为方便起见,不自己写头文件,用javah -jni com.seaboat.bytecode.ByteCodeEncryptor生成。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_seaboat_bytecode_ByteCodeEncryptor */

#ifndef _Included_com_seaboat_bytecode_ByteCodeEncryptor
#define _Included_com_seaboat_bytecode_ByteCodeEncryptor
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_seaboat_bytecode_ByteCodeEncryptor
 * Method:    encrypt
 * Signature: ([B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_com_seaboat_bytecode_ByteCodeEncryptor_encrypt
  (JNIEnv *, jclass, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif
  • 编写源文件,实现头文件声明的函数。
#include "com_seaboat_bytecode_ByteCodeEncryptor.h"
#include "jni.h"

void encode(char *str)
{
    unsigned int m = strlen(str);
    for (int i = 0; i < m; i++)
    {
        str[i] = str[i]+4;
    }

}

extern"C" JNIEXPORT jbyteArray JNICALL
Java_com_seaboat_bytecode_ByteCodeEncryptor_encrypt(JNIEnv * env, jclass cla,jbyteArray text)
{
    char* dst = (char*)env->GetByteArrayElements(text, 0);
    encode(dst);
    env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst);
    return text;
}
  • 用cl进行编译,生成动态库,指定编译需要的一些头文件。
cl /EHsc -ID:\Java\jdk1.8.0_73\include\ -ID:\Java\jdk1.8.0_73\include\win32 -LD com_seaboat_bytecode_ByteCodeEncryptor.cpp -FeByteCodeEncryptor.dll
  • 可以调用Java层的ByteCodeEncryptor类的encrypt方法了。

怎么加载动态库

Java层需要调用System.loadLibrary去加载动态库,而它其实就是通过ClassLoaderloadLibrary方法来加载,加载的大致逻辑为:

  1. 是不是使用了绝对路径来指定动态库,如果是则直接通过绝对路径来加载。
  2. 如果启动Java时带有-Dsun.boot.library.path=xxxx时,则去改参数指定的目录下寻找动态库。
  3. 如果启动Java时带有-Djava.library.path=xxxx时,则去改参数指定的目录下寻找动态库。

    加载动态库在Java层面实现不了,所以必须会通过本地才能真正实现加载操作,Java层面最后是走到NativeLibrary类,其包含的load本地方法为真正的加载注册操作。

对应着ClassLoader.cJava_java_lang_ClassLoader_00024NativeLibrary_load函数,因为NativeLibrary在Java层的ClassLoader的子类,所以其中包含一串数字00024,即表示美元符号。该函数最重要的一步是调了JVM_LoadLibrary函数,该函数如下,核心的一步是os::dll_load,它会根据不同的操作系统做不同的处理。

JVM_ENTRY_NO_ENV(void*, JVM_LoadLibrary(const char* name))
  //%note jvm_ct
  JVMWrapper2("JVM_LoadLibrary (%s)", name);
  char ebuf[1024];
  void *load_result;
  {
    ThreadToNativeFromVM ttnfvm(thread);
    load_result = os::dll_load(name, ebuf, sizeof ebuf);
  }
  if (load_result == NULL) {
    char msg[1024];
    jio_snprintf(msg, sizeof msg, "%s: %s", name, ebuf);
    // Since 'ebuf' may contain a string encoded using
    // platform encoding scheme, we need to pass
    // Exceptions::unsafe_to_utf8 to the new_exception method
    // as the last argument. See bug 6367357.
    Handle h_exception =
      Exceptions::new_exception(thread,
                                vmSymbols::java_lang_UnsatisfiedLinkError(),
                                msg, Exceptions::unsafe_to_utf8);

    THROW_HANDLE_0(h_exception);
  }
  return load_result;
JVM_END

看一个图,它包含了linuxsolariswindows三大类型操作系统的处理,下面分别看看不同操作系统如何处理。

  • 对于linux,主要通过dlopen函数来打开动态库,并加载到内存中,再通过dlsym函数可以获取动态库中的函数指针,于是就能实现调用动态库某函数。
  • 对于solaris,主要通过dlopen函数来打开动态库,并加载到内存中,再通过dlsym函数可以获取动态库中的函数指针,但它与linux不同的是dlsym在linux中是非线程安全的,需要加锁,而solaris则不需要。
  • 对于windows,主要通过LoadLibrary函数加载动态库,加载到内存中,再通过GetProcAddress函数可以获取动态库的函数指针,从而实现调用动态库某函数。

另外,我们注意到Java层不必指定动态库的后缀,这个留给JVM去解决,它会根据不同操作系统添加不同的后缀,这个逻辑由System.cJava_java_lang_System_mapLibraryName函数实现,它会有如下两个后缀。

#define JNI_LIB_SUFFIX ".so"

#define JNI_LIB_SUFFIX ".dll"

字节码

对于字节码,它是Java执行时的指令,其实想一下就能想到本地方法要在执行时区别于Java层的调用,所以必须要有一个flag来标识本地方法,那咱们用javap来看看上面包含本地方法的class会有什么标识,可以看到存在一个ACC_NATIVE,有了它就可以在执行时调用C/C++函数了。

public static native byte[] encrypt(byte[]);
    descriptor: ([B)[B
    flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE

总结一下

两句话总结起来就是,Java编译器将包含本地方法的class对应的方法添加ACC_NATIVE标识,而JVM负责将动态库加载到内存,Java执行引擎执行到本地方法时找到对应的函数,完成本地方法的调用。

以下是广告相关阅读

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以到 https://item.jd.com/12185360.html 进行预定。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

=========================

相关阅读:

注解的原理又是怎么一回事

欢迎关注:

Java调用本地方法又是怎么一回事的更多相关文章

  1. java调用本地方法的时候报错 could not find the main class:xx.program will exit

    如图所示,当在java调用本地方法的时候报错 我的解决办法是把dll文件放到System.out.println(System.getProperty("java.library.path& ...

  2. java高级用法之:调用本地方法的利器JNA

    目录 简介 JNA初探 JNA加载native lib的流程 本地方法中的结构体参数 总结 简介 JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做java native inter ...

  3. Android使用JNI(从java调用本地函数)

    当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...

  4. Android Java访问本地方法(JNI)

    当功能需要本地代码实现的时候,Java 代码就需要调用本地代码. 在调用本地代码时,首先要保证本地代码被加载到 Java 执行环境中并与 Java 代码连接在一起,这样 Java 代码在调用本地方法时 ...

  5. Java调用WebService方法总结(8)--soap.jar调用WebService

    Apache的soap.jar是一种历史很久远的WebService技术,大概是2001年左右的技术,所需soap.jar可以在http://archive.apache.org/dist/ws/so ...

  6. Java调用WebService方法总结(6)--XFire调用WebService

    XFire是codeHaus组织提供的一个WebService开源框架,目前已被Apache的CXF所取代,已很少有人用了,这里简单记录下其调用WebService使用方法.官网现已不提供下载,可以到 ...

  7. Java调用WebService方法总结(2)--JAX-WS调用WebService

    用JAX-WS(Java API for XML Web Services)调用WebService不需要引入其他框架,都是JDK自带的:文中所使用到的软件版本:Java 1.8.0_191.Dom4 ...

  8. Java调用WebService方法总结(1)--准备工作

    WebService是一种跨编程语言.跨操作系统平台的远程调用技术,已存在很多年了,很多接口也都是通过WebService方式来发布的:本系列文章主要介绍Java调用WebService的各种方法,使 ...

  9. java native本地方法详解(转)

    文章链接出处: 详解native方法的使用 自己实现一个Native方法的调用 JNI 开始本篇的内容之前,首先要讲一下JNI.Java很好,使用的人很多.应用极 广,但是Java不是完美的.Java ...

随机推荐

  1. 20145313张雪纯 《Java程序设计》8周学习总结

    20145313张雪纯 <Java程序设计>8周学习总结 教材学习内容总结 java.util.logging包的优点在于提供了日志功能相关类与接口,不必额外配置日志组件就可以在标准jav ...

  2. 20145321 《Java程序设计》课程总结

    20145321 <Java程序设计>课程总结 读书笔记链接汇总 第一周读书笔记 第二周读书笔记 第三周读书笔记 第四周读书笔记 第五周读书笔记 第六周读书笔记 第七周读书笔记 第八周读书 ...

  3. hadoop2对应的eclipse插件使用

    1. eclipse插件安装步骤: a).把插件复制到eclipse安装目录plugins文件夹下 b).打开eclipse的Window---Preference---Hadoop M/R---指向 ...

  4. G - 锘爷考驾照

    2017-07-17 11:56:06 writer:pprp 题目描述 大家都知道,锘爷是XDUdp第一人,所以锘爷决定要去考驾照!(这很有逻辑吧),他为了一次考到驾照,于是买了一辆越野车从学校开回 ...

  5. P4开源Tutorials教程样例实战及对P4v1.1规范的初分析

    Github链接:Github-P4Lang-Tutorials-p4v1.1 前言 本文主要对Barefoot开源教程中的p4v1.1实例simple_router的实战步骤进行记录与阐述,希望能帮 ...

  6. jni使用问题总结

    参考: https://blog.csdn.net/fred_lzy/article/details/53159138 https://blog.csdn.net/avi3/article/detai ...

  7. 洛谷P3601 签到题

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  8. hdu 5696 区间的价值 单调栈+rmq

    区间的价值 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Problem D ...

  9. 前端解析cookie出现多余的双引号的问题

    登录界面,用户提交后,后台获取到用户名密码,然后会设置cookie,以便于前端使用,今天在修改项目记住密码的功能的时候,读取cookie,如果存在loginInfo字段说明用户在上一次点击了记住密码的 ...

  10. Linux常用插件

    文件传输 虚拟机用xshell连接时,拖拽传输插件 yum install lrzsz setup界面化工具 yum install setuptool yum install ntsysv    # ...