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 ...
随机推荐
- Finder Quick Menu FAQ
How to use Finder Quick Menur: 1. Start Finder Quick Menu.2. Open "System Preferences -> Ext ...
- C#设计模式之控制反转即依赖注入-微软提供的Unity
使用VS2015的Nuget管理器下载Unity. 程序员接口类: 1 namespace UnityDemo 2 { 3 public interface IProgrammer 4 { 5 voi ...
- Windows下安装mayavi2
由于要使用mayavi2画三维图,但是没有找到二进制包,所以只能安装pythonxy或者canopy之类的版本,后来在http://www.lfd.uci.edu/~gohlke/pythonlibs ...
- Javascript实用技巧
1. 给参数赋默认值 //通常写法 function dateRender(format){ if(format){ format = 'Y-m-d'; } // code } //强推 functi ...
- 简单易用的分页类实例代码PHP
<?php /*********************************************** * @类名: page * @参数: $myde_total - 总记录数 * $m ...
- 获取用户真实IP,php实现
function get_client_ip() { if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv(" ...
- Google Java编程风格指南中文版(转)
作者:Hawstein出处:http://hawstein.com/posts/google-java-style.html声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Cre ...
- 在lnmp下开启fileinfo扩展 Ubuntu系统
在lnmp下开启fileinfo扩展 Ubuntu系统 1.进入目录下 cd /usr/local/lnmp1.4-full/src/php-5.6.31/ext/fileinfo 2.phpize处 ...
- java_zlib_资料
1.网页资料 1.1.http://bbs.csdn.net/topics/190020986 1.2. http://cdn.verydemo.com/demo_c89_i166794.html h ...
- 三、nginx 编译参数
命令 --prefix=/usr/share/nginx # nginx 帮助目录 --sbin-path=/usr/sbin/nginx # nginx 执行命令 --modules-path=/u ...