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核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。
=========================
相關閱讀:
註解的原理又是怎麼一回事
歡迎關注: