【Hotspot】 JNI/JNA调用、Native调用

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函数:
在源码编译过后会自动生成这些这些 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);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值