JNI即Java Native Interface,它能在Java层实现对本地方法的调用,通常本地的实现语言主要是C/C++,其实从虚拟机层面来看JNI挺好理解,JVM主要使用C/C++ 和少许汇编编写,在执行Java字节码时若是遇到有某个方法标明为Native的则从JVM中找到对应的C/C++函数,通常本地方法对应的函数会被注册到JVM中。html
使用JNI能让Java与本地语言交互,但通常也意味着丧失了跨平台性,而有些场合会使用。好比标准的Java特性不符合你的需求时,好比在性能要求很高的某段逻辑。java
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 /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层须要调用System.loadLibrary
去加载动态库,而它其实就是经过ClassLoader
的loadLibrary
方法来加载,加载的大体逻辑为:linux
-Dsun.boot.library.path=xxxx
时,则去改参数指定的目录下寻找动态库。-Djava.library.path=xxxx
时,则去改参数指定的目录下寻找动态库。NativeLibrary
类,其包含的load
本地方法为真正的加载注册操做。对应着ClassLoader.c
的Java_java_lang_ClassLoader_00024NativeLibrary_load函数,由于NativeLibrary在Java层的ClassLoader的子类,因此其中包含一串数字00024
,即表示美圆符号。该函数最重要的一步是调了JVM_LoadLibrary
函数,该函数以下,核心的一步是os::dll_load
,它会根据不一样的操做系统作不一样的处理。windows
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
三大类型操做系统的处理,下面分别看看不一样操做系统如何处理。安全
dlopen
函数来打开动态库,并加载到内存中,再经过dlsym函数能够获取动态库中的函数指针,因而就能实现调用动态库某函数。dlopen
函数来打开动态库,并加载到内存中,再经过dlsym函数能够获取动态库中的函数指针,但它与linux不一样的是dlsym在linux中是非线程安全的,须要加锁,而solaris则不须要。LoadLibrary
函数加载动态库,加载到内存中,再经过GetProcAddress函数能够获取动态库的函数指针,从而实现调用动态库某函数。另外,咱们注意到Java层没必要指定动态库的后缀,这个留给JVM去解决,它会根据不一样操做系统添加不一样的后缀,这个逻辑由System.c
的Java_java_lang_System_mapLibraryName
函数实现,它会有以下两个后缀。bash
#define JNI_LIB_SUFFIX ".so"
#define JNI_LIB_SUFFIX ".dll"复制代码
对于字节码,它是Java执行时的指令,其实想一下就能想到本地方法要在执行时区别于Java层的调用,因此必需要有一个flag来标识本地方法,那我们用javap来看看上面包含本地方法的class会有什么标识,能够看到存在一个ACC_NATIVE
,有了它就能够在执行时调用C/C++函数了。app
public static native byte[] encrypt(byte[]);
descriptor: ([B)[B
flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE复制代码
两句话总结起来就是,Java编译器将包含本地方法的class对应的方法添加ACC_NATIVE
标识,而JVM负责将动态库加载到内存,Java执行引擎执行到本地方法时找到对应的函数,完成本地方法的调用。jvm
如下是广告和相关阅读函数
========广告时间========性能
鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有须要的朋友能够到 item.jd.com/12185360.ht… 进行预约。感谢各位朋友。
=========================
相关阅读:
注解的原理又是怎么一回事
欢迎关注: