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 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三大类型操作系统的处理,下面分别看看不同操作系统如何处理。

232d200c3e8ba592fd5c8c4b939c3007.png

对于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内核设计剖析》已经在京东销售了,有需要的朋友可以到 item.jd.com/12185360.ht… 进行预定。感谢各位朋友。

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

欢迎关注:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值