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… 進行預約。感謝各位朋友。
=========================
相關閱讀:
註解的原理又是怎麼一回事
歡迎關注: