java本地方法出现问题怎么办_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
/* 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调用本地方法又是怎么一回事相关推荐
- java 调用对象的方法_JAVA调用对象方法的执行过程
JAVA调用对象方法的执行过程: ①.编译器查看对象的声明类型和方法名.假设调用x.f(parameter), 且隐式参数x声明为C类型的对象,有可能在C对象中存在多个参数类型和参数个数不同的f的方 ...
- Java同一个线程对象能否多次调用start方法
同一个线程对象能否多次调用start方法,搞清楚这个问题,首先需要了解线程的生命周期 一.线程生命周期 更多线程状态细节描述可查看Thread内部枚举类:State 从上图线程状态转换图可以看出: 新 ...
- JAVA——Scanner类绑定System.in后调用close()方法所引发的错误及其解决方案
基本概念 Java通过系统类System实现标准输入/输出的功能 通过实例化Scanner类的方式实现从键盘的数据输入 问题描述 错误示例一: import java.util.Scanner;pub ...
- java方法的参数类型_Java 基础 14 方法的重载 与 方法参数类型详解
1.1 方法重载的概述和特点 方法重载概述 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可. 方法重载特点 与返回值类型无关,只看方法名和参数列表 在调用时,虚拟机通 ...
- 写出一下Java方法对应的签名_Java中的方法签名是否包含其返回类型?
Java类/接口中的方法签名是否包括其返回类型? 例: Java是否知道这两种方法之间的区别: public class Foo { public int myMethod(int param) { ...
- java 多态与重载的区别_java实现多态 方法的重写和重载的区别
1.方法的重写 思路:先写一个父类People,在类中定义一个 print方法 ,然后写一个子类 Student 继承父类,重写print方法. //父类 class People{ public v ...
- java中skip是什么意思_Java PushbackReader skip()方法与示例
PushbackReader类skip()方法skip()方法在java.io包中可用. skip()方法用于从此PushbackReader流中跳过给定数量的字符,它将阻塞直到存在某些字符输入或发生 ...
- java中skip是什么意思_Java LineNumberInputStream skip()方法与示例
LineNumberInputStream类skip()方法skip()方法在java.io包中可用. skip()方法用于从此LineNumberInputStream流中跳过给定数目的数据字节. ...
- android调用js函数方法,Android和JavaScript相互调用的方法
本文实例讲述了Android和JavaScript相互调用的方法.分享给大家供大家参考,具体如下: Html页面和Java代码结合的方式一般用在界面经常被更改 的情况下,可以讲html放在网络中,软件 ...
最新文章
- 二叉树的建立和遍历程序代码(Java,C)
- BZOJ 2752: [HAOI2012]高速公路(road)
- WebRTC / Jitsi / 架构
- JavaScript命名空间
- C++ 私有成员变量的理解
- 乐高ev3 读取外部数据_数据就是新乐高
- 【ffmpeg for wince】音视频编解码多平台移植(for window/wince))ffmpeg
- MySQL的SQL 语句:根据从表记录个数对主表排序
- 怎么重置unity界面_请问itween如何重置?
- Android Studio GPU/CPU/Network/Memory monitor使用
- 第二十一章 基于鹰栖息(eagle perching)的无模型优化
- 苹果双系统怎么切换_华为双系统,你会用吗?实在太神了!一键开启,一台手机当两台用...
- Flash cs4快捷方式
- 舍得网开发者碰到的问题及解决(转)
- stm32 国产QMC5883L 进口HMC5883 三轴电子指南针加速度传感器
- [Java,IDEA]连接oracle的关于oracle.jdbc.driver.OracleDriver一直驱动加载失败的原因
- 时尚唯美婚礼视频制作AE标题模板 Wedding Responsive Titles
- pdf上的文字和图片内容怎么编辑
- 后端接口重定向_Java访问重定向接口
- [电路]2-独立源和受控源
热门文章
- canvas动画循环
- 凌云(LingCloud)云计算系统开源版本发布
- 在HTML中将垂直转换为平行,高中必修2数学知识点
- 快解析DDNS 无需公网ip 安全高效
- 瘦子的肠道菌群和胖子的区别_明明吃同样的饭,为什么只有你胖了?
- MacOS搭建OMNeTpp+Veins+SUMO车联网仿真平台
- cfg桩设备型号_武威CFG桩设备-施工
- 吕梁学院计算机重修,关于录入学生重修成绩的通知
- Linux 安装 RabbitMq 详细过程(含 Erlang 下载 网盘地址)
- matlab app designer 新建应用打开mat文件绘图