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

/* 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 jbyteArrayJNICALLJava_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去加载动态库,而它其实就是通过ClassLoader的loadLibrary方法来加载,加载的大致逻辑为:

是不是使用了绝对路径来指定动态库,如果是则直接通过绝对路径来加载。

如果启动Java时带有-Dsun.boot.library.path=xxxx时,则去改参数指定的目录下寻找动态库。

如果启动Java时带有-Djava.library.path=xxxx时,则去改参数指定的目录下寻找动态库。

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

对应着ClassLoader.c的Java_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

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

对于linux,主要通过dlopen函数来打开动态库,并加载到内存中,再通过dlsym函数可以获取动态库中的函数指针,于是就能实现调用动态库某函数。

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

对于windows,主要通过LoadLibrary函数加载动态库,加载到内存中,再通过GetProcAddress函数可以获取动态库的函数指针,从而实现调用动态库某函数。

另外,我们注意到Java层不必指定动态库的后缀,这个留给JVM去解决,它会根据不同操作系统添加不同的后缀,这个逻辑由System.c的Java_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 进行预定。感谢各位朋友。

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

欢迎关注:

java本地方法出现问题怎么办_Java调用本地方法又是怎么一回事相关推荐

  1. java 调用对象的方法_JAVA调用对象方法的执行过程

    JAVA调用对象方法的执行过程: ①.编译器查看对象的声明类型和方法名.假设调用x.f(parameter),  且隐式参数x声明为C类型的对象,有可能在C对象中存在多个参数类型和参数个数不同的f的方 ...

  2. Java同一个线程对象能否多次调用start方法

    同一个线程对象能否多次调用start方法,搞清楚这个问题,首先需要了解线程的生命周期 一.线程生命周期 更多线程状态细节描述可查看Thread内部枚举类:State 从上图线程状态转换图可以看出: 新 ...

  3. JAVA——Scanner类绑定System.in后调用close()方法所引发的错误及其解决方案

    基本概念 Java通过系统类System实现标准输入/输出的功能 通过实例化Scanner类的方式实现从键盘的数据输入 问题描述 错误示例一: import java.util.Scanner;pub ...

  4. java方法的参数类型_Java 基础 14 方法的重载 与 方法参数类型详解

    1.1 方法重载的概述和特点 方法重载概述 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可. 方法重载特点 与返回值类型无关,只看方法名和参数列表 在调用时,虚拟机通 ...

  5. 写出一下Java方法对应的签名_Java中的方法签名是否包含其返回类型?

    Java类/接口中的方法签名是否包括其返回类型? 例: Java是否知道这两种方法之间的区别: public class Foo { public int  myMethod(int param) { ...

  6. java 多态与重载的区别_java实现多态 方法的重写和重载的区别

    1.方法的重写 思路:先写一个父类People,在类中定义一个 print方法 ,然后写一个子类 Student 继承父类,重写print方法. //父类 class People{ public v ...

  7. java中skip是什么意思_Java PushbackReader skip()方法与示例

    PushbackReader类skip()方法skip()方法在java.io包中可用. skip()方法用于从此PushbackReader流中跳过给定数量的字符,它将阻塞直到存在某些字符输入或发生 ...

  8. java中skip是什么意思_Java LineNumberInputStream skip()方法与示例

    LineNumberInputStream类skip()方法skip()方法在java.io包中可用. skip()方法用于从此LineNumberInputStream流中跳过给定数目的数据字节. ...

  9. android调用js函数方法,Android和JavaScript相互调用的方法

    本文实例讲述了Android和JavaScript相互调用的方法.分享给大家供大家参考,具体如下: Html页面和Java代码结合的方式一般用在界面经常被更改 的情况下,可以讲html放在网络中,软件 ...

最新文章

  1. 二叉树的建立和遍历程序代码(Java,C)
  2. BZOJ 2752: [HAOI2012]高速公路(road)
  3. WebRTC / Jitsi / 架构
  4. JavaScript命名空间
  5. C++ 私有成员变量的理解
  6. 乐高ev3 读取外部数据_数据就是新乐高
  7. 【ffmpeg for wince】音视频编解码多平台移植(for window/wince))ffmpeg
  8. MySQL的SQL 语句:根据从表记录个数对主表排序
  9. 怎么重置unity界面_请问itween如何重置?
  10. Android Studio GPU/CPU/Network/Memory monitor使用
  11. 第二十一章 基于鹰栖息(eagle perching)的无模型优化
  12. 苹果双系统怎么切换_华为双系统,你会用吗?实在太神了!一键开启,一台手机当两台用...
  13. Flash cs4快捷方式
  14. 舍得网开发者碰到的问题及解决(转)
  15. stm32 国产QMC5883L 进口HMC5883 三轴电子指南针加速度传感器
  16. [Java,IDEA]连接oracle的关于oracle.jdbc.driver.OracleDriver一直驱动加载失败的原因
  17. 时尚唯美婚礼视频制作AE标题模板 Wedding Responsive Titles
  18. pdf上的文字和图片内容怎么编辑
  19. 后端接口重定向_Java访问重定向接口
  20. [电路]2-独立源和受控源

热门文章

  1. canvas动画循环
  2. 凌云(LingCloud)云计算系统开源版本发布
  3. 在HTML中将垂直转换为平行,高中必修2数学知识点
  4. 快解析DDNS 无需公网ip 安全高效
  5. 瘦子的肠道菌群和胖子的区别_明明吃同样的饭,为什么只有你胖了?
  6. MacOS搭建OMNeTpp+Veins+SUMO车联网仿真平台
  7. cfg桩设备型号_武威CFG桩设备-施工
  8. 吕梁学院计算机重修,关于录入学生重修成绩的通知
  9. Linux 安装 RabbitMq 详细过程(含 Erlang 下载 网盘地址)
  10. matlab app designer 新建应用打开mat文件绘图