1.在hotspot中,JNI和Native是什么
JNI:
JDK1.1中出现了JNI,这就为java代码调用c/c++动态链接库提供了一种方法。
若调用本地的C/C++的动态链接库,我们首先要通过java实现自己的本地方法。
我们只需要利用JNI提供的关键字native把方法声明为是本地方法(由其他语言是实现的方法)。
然后再利用c/c++实现一个有相同方法名的动态链接库,并放在指定目录下即可供Java调用即可。
JNA:
JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。
开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。
Native:
native 是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。
这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。
native方法是通过java中的JNI实现的。
2.JNI如何使用
流程是先写 Java 文件 HelloWorld.java,里面定义 Native 函数 say(String content),不用写函数实现。
然后使用 javah 工具生成对应函数的头文件:javah -jni -v -o HelloWorld.h jni.HelloWorld
将头文件 HelloWorld.h 引入 C++ 中实现头文件定义的函数,然后打成 DLL 动态库。再将DLL放到操作系统的PATH中,就能够实现 Java 调用 C/C++ 了。
其中生成的 HelloWorld.h 头文件中会定义这个函数:
JNIEXPORT void JNICALL Java_jni_HelloWorld_say
(JNIEnv *, jclass, jstring);
另外DLL文件使用 System.load() 进行加载,也就是通过 :
System.load() =>
ClassLoader.loadLibrary0 =>
ClassLoader.findBuiltinLib() => 是个 native 方法,会进入 Hotspot 中的 Java_java_lang_ClassLoader_findBuiltinLib() 函数中。
除了 javah 头文件方式,还有一种 JNI_OnLoad() 方式,也就是 C 代码中定义 JNI_OnLoad() 这个函数会在 Java 加载 dll 库时自动调用,在这里面调用 (*env)->RegisterNatives,进行方法注册,例如:
Java代码:
package com.jni;
public class JavaHello {
public static native String hello();
static {
// load library: libtest.so
try {
System.loadLibrary("test");
} catch (UnsatisfiedLinkError ule) {
System.err.println("WARNING: Could not load library!");
}
}
public static void main(String[] args) {
String s = new JavaHello().hello();
System.out.println(s);
}
}
C代码:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)
{
printf("hello in c native code./n");
return (*env)->NewStringUTF(env, "hello world returned.");
}
#define JNIREG_CLASS "com/jni/JavaHello"//指定要注册的类
static JNINativeMethod gMethods[] = {
{ "hello", "()Ljava/lang/String;", (void*)native_hello },//绑定
};
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env){
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注册
return -1;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
3.使用 JNA。
这里通过 sun 开发的 JNA 调用 native 函数来进行测试,Java 调用 DLL 库这样进行:
依赖
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.11.0</version>
</dependency>
定义 CANControl.java 加载动态库,定义同名库函数进行调用:
public interface CANControl extends Library {
// 加载库
CANControl INSTANCE = Native.load("./libs/ControlCAN.dll", CANControl.class);
// 定义 DLL中同名函数
int VCI_OpenDevice(int devType, int devIndex, int reserved);
// main 中进行调用
public static void main(String[] args) {
// 调用接口
MathDll.instance.VCI_OpenDevice(1,1,1);
}
}
可以看到直接定义同名Java函数即可。
4.调用 Hotshot 中的 Native 函数。
在 Hotspot 中已经实现好了一些 Native 函数,上层 Jave 可以直接调用,都是一些系统相关函数,例如一些常见Native函数:
![](https://img-blog.csdnimg.cn/72967ebba054441d9e7a3b321cf43328.png)
在源码编译过后会自动生成这些这些 JavaClass 对应的头文件,里面定义了 JavaClass 的 Native Method 映射关系:
jdk/gensrc_headers/java_lang_Thread.h
/*
* Class: java_lang_Thread
* Method: registerNatives
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_java_lang_Thread_registerNatives
(JNIEnv *, jclass);
例如 Thread.c:
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
这些 Java 源码在初始静态块中会调用自己的 redisterNatives() ,然后调用 C 源码的 RegisterNatives() 函数:
Thread.java:
class Thread implements Runnable {
private static native void registerNatives();
static {
registerNatives();
}
}
Thread.c:
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
这行代码中的 env 是 jni 环境,会进入 jni_RegisterNatives(),用于遍历 JNINativeMethod 中定义了的 Native 函数进行绑定注册。
jni_RegisterNatives()
JNI_ENTRY(jint, jni_RegisterNatives(JNIEnv *env, jclass clazz,const JNINativeMethod *methods,jint nMethods))
JNIWrapper("RegisterNatives");
#ifndef USDT2
DTRACE_PROBE4(hotspot_jni, RegisterNatives__entry, env, clazz, methods, nMethods);
#else /* USDT2 */
HOTSPOT_JNI_REGISTERNATIVES_ENTRY(env, clazz, (void *) methods, nMethods);
#endif /* USDT2 */
jint ret = 0;
DT_RETURN_MARK(RegisterNatives, jint, (const jint&)ret);
// 获取方法所属的klass
KlassHandle h_k(thread, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
// 遍历JNINativeMethod数组
for (int index = 0; index < nMethods; index++) {
const char* meth_name = methods[index].name;
const char* meth_sig = methods[index].signature;
int meth_name_len = (int)strlen(meth_name);
// 获取方法名和方法签名在符号表中对应的符号,因为指定方法已经完成加载所以这两个符号都已经存在
TempNewSymbol name = SymbolTable::probe(meth_name, meth_name_len);
TempNewSymbol signature = SymbolTable::probe(meth_sig, (int)strlen(meth_sig));
// 省略
// 设置native_function,fnPtr属性就是对应本地方法的入口地址
bool res = register_native(h_k, name, signature,(address) methods[index].fnPtr, THREAD);
if (!res) {
ret = -1;
break;
}
}
return ret;
JNI_END
其中 register_native() 函数会将本地方法入口地址设置到 Method::native_function 上:
static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {
Method* method = k()->lookup_method(name, signature);
if (method == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s name or signature does not match",
Method::name_and_sig_as_C_string(k(), name, signature));
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
}
if (!method->is_native()) {
// trying to register to a non-native method, see if a JVM TI agent has added prefix(es)
method = find_prefixed_native(k, name, signature, THREAD);
if (method == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s is not declared as native",
Method::name_and_sig_as_C_string(k(), name, signature));
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
}
}
if (entry != NULL) {
method->set_native_function(entry,Method::native_bind_event_is_interesting);
} else {
method->clear_native_function();
}
if (PrintJNIResolving) {
ResourceMark rm(THREAD);
tty->print_cr("[Registering JNI native method %s.%s]",
method->method_holder()->external_name(),
method->name()->as_C_string());
}
return true;
}
例如 Thread.c 会进行下列注册:
meth_name=start0
meth_name=stop0
meth_name=isAlive
meth_name=suspend0
meth_name=resume0
meth_name=setPriority0
meth_name=yield
meth_name=sleep
meth_name=currentThread
meth_name=countStackFrames
meth_name=interrupt0
meth_name=isInterrupted
meth_name=holdsLock
meth_name=getThreads
meth_name=dumpThreads
meth_name=setNativeName
另外除了显示绑定之外,在遵循javah头文件的命名规范下本地方法与本地实现的绑定,是一种被动的绑定,在本地方法第一次执行的时候才会完成绑定,会判断 method 是否已经绑定,未绑定的话则执行绑定。
因为前面 Java Static 中调用的 RegisterNatives 函数用于 JNI 注册,而底层调用的 Java_java_lang_Xxxx_registerNatives() 这些函数本身也是一个 JNI ,那么这些起始的 JNI 函数就是通过 NativeLookup::lookup() 进行注册的。
NativeLookup::lookup()
address NativeLookup::lookup(methodHandle method, bool& in_base_library, TRAPS) {
// 如果没有绑定本地代码实现
if (!method->has_native_function()) {
address entry = lookup_base(method, in_base_library, CHECK_NULL);
// 设置native_function
method->set_native_function(entry,Method::native_bind_event_is_interesting);
// -verbose:jni printing //打印日志
if (PrintJNIResolving) {
ResourceMark rm(THREAD);
tty->print_cr("[Dynamic-linking native method %s.%s ... JNI]",
method->method_holder()->external_name(),
method->name()->as_C_string());
}
}
return method->native_function();
}
address NativeLookup::lookup_base(methodHandle method, bool& in_base_library, TRAPS) {
address entry = NULL;
ResourceMark rm(THREAD);
// 获取entry
entry = lookup_entry(method, in_base_library, THREAD);
if (entry != NULL) return entry;
entry = lookup_entry_prefixed(method, in_base_library, THREAD);
if (entry != NULL) return entry;
THROW_MSG_0(vmSymbols::java_lang_UnsatisfiedLinkError(),method->name_and_sig_as_C_string());
}
address NativeLookup::lookup_entry(methodHandle method, bool& in_base_library, TRAPS) {
address entry = NULL;
in_base_library = false;
// 获取jni方法名,由Java_klass_name_method_name拼出来的
char* pure_name = pure_jni_name(method);
// 获取方法参数个数
int args_size = 1 + (method->is_static() ? 1 : 0) + method->size_of_parameters();
//分别在不同命名规范下尝试查找匹配的本地方法实现,通常是使用第一种格式
// 1) Try JNI short style
entry = lookup_style(method, pure_name, "", args_size, true, in_base_library, CHECK_NULL);
if (entry != NULL) return entry;
// Compute long name
char* long_name = long_jni_name(method);
// 2) Try JNI long style
entry = lookup_style(method, pure_name, long_name, args_size, true, in_base_library, CHECK_NULL);
if (entry != NULL) return entry;
// 3) Try JNI short style without os prefix/suffix
entry = lookup_style(method, pure_name, "", args_size, false, in_base_library, CHECK_NULL);
if (entry != NULL) return entry;
// 4) Try JNI long style without os prefix/suffix
entry = lookup_style(method, pure_name, long_name, args_size, false, in_base_library, CHECK_NULL);
return entry; // NULL indicates not found
}
根据调试可以返现通过 NativeLookup::lookup() 注册的函数有这一些,可以看到基本上是注册的 _registerNatives 函数:
NativeLookup::lookup_entry() 注册:Java_java_lang_Object_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_lang_System_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_lang_Thread_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_security_AccessController_getStackAccessControlContext
NativeLookup::lookup_entry() 注册:Java_java_security_AccessController_getInheritedAccessControlContext
NativeLookup::lookup_entry() 注册:Java_java_lang_Class_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_security_AccessController_doPrivileged
NativeLookup::lookup_entry() 注册:Java_java_lang_ClassLoader_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_lang_Class_getPrimitiveClass
NativeLookup::lookup_entry() 注册:Java_java_lang_Float_floatToRawIntBits
NativeLookup::lookup_entry() 注册:Java_java_lang_Double_doubleToRawLongBits
NativeLookup::lookup_entry() 注册:Java_java_lang_Double_longBitsToDouble
NativeLookup::lookup_entry() 注册:Java_java_lang_System_initProperties
NativeLookup::lookup_entry() 注册:Java_sun_misc_VM_initialize
NativeLookup::lookup_entry() 注册:Java_sun_misc_Unsafe_registerNatives
NativeLookup::lookup_entry() 注册:Java_java_lang_Throwable_fillInStackTrace
NativeLookup::lookup_entry() 注册:Java_sun_reflect_Reflection_getCallerClass
NativeLookup::lookup_entry() 注册:Java_java_lang_String_intern
NativeLookup::lookup_entry() 注册:Java_java_lang_Object_getClass
NativeLookup::lookup_entry() 注册:Java_java_lang_Class_forName0
NativeLookup::lookup_entry() 注册:Java_sun_reflect_Reflection_getClassAccessFlags
NativeLookup::lookup_entry() 注册:Java_sun_reflect_NativeConstructorAccessorImpl_newInstance0
NativeLookup::lookup_entry() 注册:Java_java_lang_Runtime_maxMemory
NativeLookup::lookup_entry() 注册:Java_java_io_FileInputStream_initIDs
NativeLookup::lookup_entry() 注册:Java_java_io_FileDescriptor_initIDs
NativeLookup::lookup_entry() 注册:Java_java_io_FileOutputStream_initIDs
NativeLookup::lookup_entry() 注册:Java_java_security_AccessController_doPrivileged
NativeLookup::lookup_entry() 注册:Java_java_lang_System_setIn0
不管是哪种绑定,最终都会执行下面这行代码,这一行实际就是注册的含义:
// 设置native_function
method->set_native_function(entry,Method::native_bind_event_is_interesting);
后面 native 方法在 stub 调用时,也是根据 Method::native_function 找到方法实现进行调用的。
5.构造 entry 调用 native 方法
前面知道了 NativeLookup::lookup 可以绑定 _registerNatives 函数,但是在什么实际去绑定的呢?实际上是在需要调用 native 函数时,先判断是否绑定如果未绑定才调用 NativeLookup::lookup 进行绑定。
也就是说 NativeLookup::lookup 是 native 方法在第一次执行时才会调用。和 C++ 调用普通方法一样同样是构造 entry需要生成 native 方法的 stub 来进行调用。
也就是 InterpreterGenerator::generate_native_entry() 函数中 // get native function entry point 注释的那一部分代码:
address InterpreterGenerator::generate_native_entry(bool synchronized) {
// 省略
// get native function entry point
{
Label L;
__ movptr(rax, Address(method, Method::native_function_offset()));
ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry());
__ movptr(rscratch2, unsatisfied.addr());
__ cmpptr(rax, rscratch2);
__ jcc(Assembler::notEqual, L);
__ call_VM(noreg,
CAST_FROM_FN_PTR(address,InterpreterRuntime::prepare_native_call),
method);
__ get_method(method);
__ movptr(rax, Address(method, Method::native_function_offset()));
__ bind(L);
}
// 省略
return entry_point;
}
会调用 InterpreterRuntime::prepare_native_call() 函数判断 Method::native_function是否值you'zh,没有值的话就会调用 NativeLookup::lookup() 进行注册。
InterpreterRuntime::prepare_native_call():
IRT_ENTRY(void, InterpreterRuntime::prepare_native_call(JavaThread* thread, Method* method))
methodHandle m(thread, method);
assert(m->is_native(), "sanity check");
bool in_base_library;
// 如果Method::native_function还没有值,需要调用NativeLookup::lookup()函数
if (!m->has_native_function()) {
NativeLookup::lookup(m, in_base_library, CHECK);
}
// 保证Method::signature_handler有值
SignatureHandlerLibrary::add(m);
IRT_END
而注册的真正含义是将本地函数的实现会放到 Method 实例的 native_function这个slot中,代码如下,这行代码也就是出现在前面 NativeLookup::lookup()、jni.cpp register_native() 函数中:
// 设置native_function
method->set_native_function(entry,Method::native_bind_event_is_interesting);