Java调用本地方法又是怎么一回事
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
去加载动态库,而它其实就是通过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调用本地方法的时候报错 could not find the main class:xx.program will exit
如图所示,当在java调用本地方法的时候报错 我的解决办法是把dll文件放到System.out.println(System.getProperty("java.library.path& ...
- java高级用法之:调用本地方法的利器JNA
目录 简介 JNA初探 JNA加载native lib的流程 本地方法中的结构体参数 总结 简介 JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做java native inter ...
- Android使用JNI(从java调用本地函数)
当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...
- Android Java访问本地方法(JNI)
当功能需要本地代码实现的时候,Java 代码就需要调用本地代码. 在调用本地代码时,首先要保证本地代码被加载到 Java 执行环境中并与 Java 代码连接在一起,这样 Java 代码在调用本地方法时 ...
- Java调用WebService方法总结(8)--soap.jar调用WebService
Apache的soap.jar是一种历史很久远的WebService技术,大概是2001年左右的技术,所需soap.jar可以在http://archive.apache.org/dist/ws/so ...
- Java调用WebService方法总结(6)--XFire调用WebService
XFire是codeHaus组织提供的一个WebService开源框架,目前已被Apache的CXF所取代,已很少有人用了,这里简单记录下其调用WebService使用方法.官网现已不提供下载,可以到 ...
- Java调用WebService方法总结(2)--JAX-WS调用WebService
用JAX-WS(Java API for XML Web Services)调用WebService不需要引入其他框架,都是JDK自带的:文中所使用到的软件版本:Java 1.8.0_191.Dom4 ...
- Java调用WebService方法总结(1)--准备工作
WebService是一种跨编程语言.跨操作系统平台的远程调用技术,已存在很多年了,很多接口也都是通过WebService方式来发布的:本系列文章主要介绍Java调用WebService的各种方法,使 ...
- java native本地方法详解(转)
文章链接出处: 详解native方法的使用 自己实现一个Native方法的调用 JNI 开始本篇的内容之前,首先要讲一下JNI.Java很好,使用的人很多.应用极 广,但是Java不是完美的.Java ...
随机推荐
- 20145313张雪纯 《Java程序设计》8周学习总结
20145313张雪纯 <Java程序设计>8周学习总结 教材学习内容总结 java.util.logging包的优点在于提供了日志功能相关类与接口,不必额外配置日志组件就可以在标准jav ...
- 20145321 《Java程序设计》课程总结
20145321 <Java程序设计>课程总结 读书笔记链接汇总 第一周读书笔记 第二周读书笔记 第三周读书笔记 第四周读书笔记 第五周读书笔记 第六周读书笔记 第七周读书笔记 第八周读书 ...
- hadoop2对应的eclipse插件使用
1. eclipse插件安装步骤: a).把插件复制到eclipse安装目录plugins文件夹下 b).打开eclipse的Window---Preference---Hadoop M/R---指向 ...
- G - 锘爷考驾照
2017-07-17 11:56:06 writer:pprp 题目描述 大家都知道,锘爷是XDUdp第一人,所以锘爷决定要去考驾照!(这很有逻辑吧),他为了一次考到驾照,于是买了一辆越野车从学校开回 ...
- P4开源Tutorials教程样例实战及对P4v1.1规范的初分析
Github链接:Github-P4Lang-Tutorials-p4v1.1 前言 本文主要对Barefoot开源教程中的p4v1.1实例simple_router的实战步骤进行记录与阐述,希望能帮 ...
- jni使用问题总结
参考: https://blog.csdn.net/fred_lzy/article/details/53159138 https://blog.csdn.net/avi3/article/detai ...
- 洛谷P3601 签到题
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- hdu 5696 区间的价值 单调栈+rmq
区间的价值 Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Problem D ...
- 前端解析cookie出现多余的双引号的问题
登录界面,用户提交后,后台获取到用户名密码,然后会设置cookie,以便于前端使用,今天在修改项目记住密码的功能的时候,读取cookie,如果存在loginInfo字段说明用户在上一次点击了记住密码的 ...
- Linux常用插件
文件传输 虚拟机用xshell连接时,拖拽传输插件 yum install lrzsz setup界面化工具 yum install setuptool yum install ntsysv # ...