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 ...
随机推荐
- linux下安装mysql详细步骤
最近买了个阿里云服务器,搭建mysql环境. 该笔记用于系统上未装过mysql的干净系统第一次安装mysql.自己指定安装目录,指定数据文件目录. linux系统版本: CentOS 7.3 64位 ...
- PHP中的_FILE_和_DIR_的区别
<?php$dir = dirname(__FILE__);?>在PHP5.3中,增加了一个新的常量__DIR__,指向当前执行的PHP脚本所在的目录.例如当前执行的PHP文件为 /www ...
- Maven取消编译自动测试
Maven取消编译自动测试 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins&l ...
- jsp中获取list长度
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ tag ...
- Objective C NSString 编码成URL 特殊字符处理
找了一下网上的教程都是使用类似以下代码,Xcode提示这个CoreFoundation不受ARC管理,所以折中的方式是添加__bridge. NSString *encodedValue = (__b ...
- Java 1.7 ThreadPoolExecutor源码解析
Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThread ...
- (转帖) 为Docker容器指定自定义网段的固定IP/静态IP地址
作者:雨水,日期:2016-04-09 CSDN博客: http://blog.csdn.net/gobitan 摘要:Docker容器运行的时候默认会自动分配一个默认网桥所在网段的IP地址.但很多 ...
- Android6.0------权限申请管理(单个权限和多个权限申请)
Android开发时,到6.0系统上之后,有的权限就得申请才能用了. Android将权限分为正常权限 和 危险权限 Android系统权限分为几个保护级别.需要了解的两个最重要保护级别是 正常权限 ...
- 设计模式--享元模式C++实现
1定义 使用共享对象可有效的支持大量细粒度的对象 2类图 角色分析 Flyweight抽象享元角色,一个产品的抽象,定义内部状态和外部状态的接口或者实现 ConcreteFlyweight具体享元角色 ...
- [nodejs]npm国内npm安装nodejs modules终极解决方案
此方案用于设置代理和修改镜像地址都不能解决问题使用 1.npm root 确认node模块的根文件夹,全局要加-g. osx同样是此命令,先清除缓存. npm cache clean C:\Users ...