美文网首页js css html
JNI和字节码方法调用

JNI和字节码方法调用

作者: 程序员札记 | 来源:发表于2022-07-06 21:06 被阅读0次

Java对象创建的本质就是按照对象的大小分配一块内存,然后完成属性的初始化。对象创建完了,接着干啥了?调用Java方法完成特定功能。这就是我们接下来探讨的主题,Java方法调用是怎么实现的。

一、Main方法
main方法是Java应用启动执行的入口方法,这个方法是怎么执行的了?,关键代码在OpenJDK jdk/src/share/bin/java.c中的int JNICALL JavaMain(void * _args)方法,如下图:

image.png

即main方法是通过JNI的CallStaticVoidMethod方法执行的。

二、JNI 方法调用
1、API定义
JNI的方法调用的API,分为三种,总结如下:

  • NativeType Call<type>Method:执行非静态方法调用,如果子类覆写父类方法,则调用子类覆写后的方法。
  • NativeType CallNonvirtual<type>Method:Call<type>Method的扩展版,如果子类覆写父类方法,可以根据jmethedId调用子类覆写后的方法或者调用父类原来的方法,前者jmethodId从子类jclass中获取,后者jmethodId从父类jclass中获取。
  • NativeType CallStatic<type>Method:执行静态方法调用。

三者根据传递参数的方式的不同,又有三个“重载”方法,以Call<type>Method为例:

  • Call<type>Method:按照Java方法中定义的参数类型和顺序依次传递参数即可,注意JNI中不支持基本类型的自动装箱拆箱,如果方法参数是包装类,则需要调用包装类的构造方法构造基本包装类实例。
  • Call<type>MethodA:按照Java方法中定义的参数类型和顺序将所有参数放入一个jvalue数组中,然后传入该数组的指针即可,jvalue是一个union类型,可以支持所有的参数类型。
    Call<type>MethodV:按照Java方法中定义的参数类型和顺序将所有参数放入一个va_list类中,该类表示一个参数列表。
    通过宏的方式实现不同NativeType和type下的方法定义,以Call<type>Method为例,如下图:
image.png

所有方法调用的API最终调用的都是jni.cpp中jni_invoke_static和jni_invoke_nonstatic方法,这两个方法的调用如下图:

image.png

其中jni_NewObject三个方法是通过jni_invoke_nonstatic调用类构造方法

image.png

2、 jni_invoke_static

static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  //resolve_jmethod_id将jmethodID转换成Method*
  methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
 
  // Create object to hold arguments for the JavaCall, and associate it with
  // the jni parser
  ResourceMark rm(THREAD);
  //获取方法参数个数
  int number_of_parameters = method->size_of_parameters();
  //初始化java_args
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);
  //校验目标方法为静态方法
  assert(method->is_static(), "method should be static");
 
  //fingerprint返回目标方法的fingerprint,是一长串数字,包含方法的参数类型,返回值类型等信息
  /解析方法参数到JavaCallArguments中
  args->iterate( Fingerprinter(method).fingerprint() );
  // 设置返回结果的结果类型
  result->set_type(args->get_ret_type());
 
  //执行方法调用
  JavaCalls::call(result, method, &java_args, CHECK);
 
  // 如果结果类型是对象或者数组类型,则需要将其转换成本地引用
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}
 
enum JNICallType {
  JNI_STATIC, //静态方法
  JNI_VIRTUAL, //虚方法
  JNI_NONVIRTUAL //非虚方法
};
 
 
 inline static Method* resolve_jmethod_id(jmethodID mid) {
    assert(mid != NULL, "JNI method id should not be null");
    //据此可知,jmethodID实际是Method的指针的指针
    return *((Method**)mid);
  }
3、jni_invoke_nonstatic
static void jni_invoke_nonstatic(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  //校验调用实例方法关联的对象receiver不能为空
  oop recv = JNIHandles::resolve(receiver);
  if (recv == NULL) {
    THROW(vmSymbols::java_lang_NullPointerException());
  }
  Handle h_recv(THREAD, recv);
 
  int number_of_parameters;
  Method* selected_method;
  {
    //将jmethodID转换成Method*
    Method* m = Method::resolve_jmethod_id(method_id);
    //获取方法个数
    number_of_parameters = m->size_of_parameters();
    //获取此方法所属的类Klass
    Klass* holder = m->method_holder();
    if (call_type != JNI_VIRTUAL) {
        //如果是非虚方法调用,即CallNonvirtual<type>Method,则使用指定的方法
        //此时jmethodID从父类Klass获取的则使用父类的实现,如果使用子类Klass的实现则使用子类的实现
        selected_method = m;
    } 
    //虚方法调用,即Call<type>Method,itable即接口方法表
    else if (!m->has_itable_index()) {
      // non-interface call -- for that little speed boost, don't handlize
      // 非接口方法调用
      debug_only(No_Safepoint_Verifier nosafepoint;)
      //校验该方法在虚方法表的索引是否有效,如果目标类已经完成链接和初始化则valid_vtable_index()方法返回true
      assert(m->valid_vtable_index(), "no valid vtable index");
      //获取虚方法表中的索引,注意同一个方法,无论从子类Klass获取还是从父类Klass获取,其vtbl_index都是一样的
      int vtbl_index = m->vtable_index();
 
      //如果vtbl_index不等于nonvirtual_vtable_index,nonvirtual_vtable_index表示该方法不需要通过vtable分发,即父类定义的final方法
      if (vtbl_index != Method::nonvirtual_vtable_index) {
        //获取receiver对应的Klass
        Klass* k = h_recv->klass();
        InstanceKlass *ik = (InstanceKlass*)k;
        //获取目标Klass在指定虚方法表索引处的虚方法实现,
        //如receiver实际是子类实例,jmethodID无论从父类Klass还是子类Klass获取的,实际调用的都是子类的实现
        selected_method = ik->method_at_vtable(vtbl_index);
      } else {
        //final方法
        selected_method = m;
      }
    } else {
      //接口方法
      KlassHandle h_holder(THREAD, holder);
      //获取接口方法表中的索引,无论jmethodID从接口类Klass还是实现类Klass获取的,其itbl_index都是一样的
      int itbl_index = m->itable_index();
      Klass* k = h_recv->klass();
      //获取接口方法,使用receiver实例实际的类的接口实现
      selected_method = InstanceKlass::cast(k)->method_at_itable(h_holder(), itbl_index, CHECK);
    }
  }
 
  methodHandle method(THREAD, selected_method);
 
  /
  ResourceMark rm(THREAD);
  //初始化JavaCallArguments
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);
 
  //校验方法不是静态方法
  assert(!method->is_static(), "method should not be static");
  //设置接受方法调用的对象实例
  args->push_receiver(h_recv); // Push jobject handle
 
  //解析方法参数
  args->iterate( Fingerprinter(method).fingerprint() );
  //设置方法返回类型
  result->set_type(args->get_ret_type());
 
  //调用方法
  JavaCalls::call(result, method, &java_args, CHECK);
 
  //处理结果返回值
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}

从上述实现可知,jni_invoke_nonstatic比jni_invoke_static就是多了确认实际调用方法的逻辑。CallNonvirtual<type>Method时和CallStatic<type>Method实际逻辑是一样的,都是执行直接执行传入的jmethodID对应的Java方法,而Call<type>Method需要根据传入的jmethodID确认目标方法的虚方法表索引或者接口方法表索引,然后根据索引获取目标实例对象所属的类的实现方法。itable和vtable可参考《Hotspot Klass模型——Java类内存表示机制》。

三、方法调用字节码指令
1、指令定义
main方法通过JNI的方法调用开始执行后,如果调用其他的Java方法就必须通过方法调用的字节码指令,JNI的方法调用仅限于本地方法实现使用。执行方法调用的字节码指令总共有5个,概述如下,详情可以参考《Java虚拟机规范》:

  • invokedynamic:调用动态方法,动态方法是指最终被调用的方法可以在运行期由程序动态指定,从JDK7引入,但是JDK7的编译器无法直接使用该指令,只能通过ASM字节码编辑工具调用。目前主要用于JDK8中java.lang.invoke包和Lambda表达式。
    -invokeinteface:调用接口方法,即由接口类中定义的方法,此时调用对象声明的类型是接口类,实际的类型是该接口的某个实现类。假如调用对象实例是C,查找具体执行方法时先在C中查找名称和描述符都和接口方法一致的方法,如果没有找到则继续在C的父类,父类的父类,不断往上递归查找,直到找到名称和描述符都和接口方法一致的方法,在编译正常的情形下通过这两步查找就可以确认实际被执行的方法。
    -invokespecial:调用实例方法,包括父类方法,私有方法和实例初始化方法三种,实际被执行的方法的查找逻辑同invokeinteface基本一样,都是现在当前类查找,再往上递归查找当前类的父类,在编译期即可确认。
    -invokestatic:调用静态方法,在方法解析成功后,如果方法所在的类或者接口没有被初始化则指令执行时会触发其初始化。因为静态方法不能被继承,因此只需在调用类中查找是否存在目标方法,也是在编译期即可确认实际被执行的方法。
    -invokevirtual:调用实例方法,依据调用对象实例的类型进行分派,如果目标方法是签名多态性方法(通常是java.lang.invoke.MethodHanlde的invoke和invokeExact方法),则需要做特殊处理,以保证方法句柄能够正常调用。注意invokevirtual并不是其字面描述的一样的调用虚方法,调用某个子类独有的方法(按C++的虚方法定义这种方法就是非虚方法)也是通过invokevirtual完成,因为JVM无法确认方法调用实例是该子类的实例,还是该子类的子类实例,后者可能覆写了该方法。

测试用例如下:

package jni;
 
 
interface InterfaceA{
    void say();
 
    //接口中的default方法只是接口方法的默认实现,接口实现类可以改写默认实现
    default void print(){
        System.out.println("InterfaceA default methoed");
    }
 
    static void staticDo(){
        System.out.println("InterfaceA staticDo");
    }
}
 
 
interface InterfaceB{
 
    void interfaceDo();
 
}
 
 
class superB implements InterfaceA{
 
    @Override
    public void say() {
        System.out.println("superB say");
    }
 
    @Override
    public void print() {
        System.out.println("superB print");
    }
 
    public void superDo(){
        System.out.println("superB superDo ");
    }
}
 
public class InvokeTest extends superB implements InterfaceB{
 
    @Override
    public void say() {
        super.say();
        System.out.println("InvokeTest say");
    }
 
    private void privateDo(){
        System.out.println("InvokeTest privateDo");
    }
 
    public void subDo(){
        privateDo();
        System.out.println("InvokeTest subDo");
    }
 
    @Override
    public void interfaceDo() {
        System.out.println("InvokeTest interfaceDo");
    }
 
    public static void main(String[] args) {
        InterfaceA.staticDo();
        InvokeTest a=new InvokeTest();
        a.say();
        a.subDo();
        a.print();
        a.superDo();
        a.interfaceDo();
 
        superB b=a;
        b.say();
        b.print();
        b.superDo();
 
        InterfaceA c=a;
        c.say();
        c.print();
 
        InterfaceB d=a;
        d.interfaceDo();
    }
}

编译过后执行javap -v可以查看具体的字节码指令,截取部分如下:

 public void say();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2                  // Method jni/superB.say:()V  调用父类方法
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #4                  // String InvokeTest say
         9: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: return
 
public void subDo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #7                  // Method privateDo:()V  调用私有方法
         4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #8                  // String InvokeTest subDo
         9: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: return
 
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: invokestatic  #10                 // InterfaceMethod jni/InterfaceA.staticDo:()V   调用静态方法
         3: new           #11                 // class jni/InvokeTest
         6: dup
         7: invokespecial #12                 // Method "<init>":()V  调用实例初始化方法
        10: astore_1
        11: aload_1
        12: invokevirtual #13                 // Method say:()V  调用实例的类型都是类,所以这里都是invokevirtual 
        15: aload_1
        16: invokevirtual #14                 // Method subDo:()V  subDo是子类特有的,为了兼容调用实例有可能是子类的子类的情形,所以依然使用invokevirtual指令
        19: aload_1
        20: invokevirtual #15                 // Method print:()V
        23: aload_1
        24: invokevirtual #16                 // Method superDo:()V
        27: aload_1
        28: invokevirtual #17                 // Method interfaceDo:()V
        31: aload_1
        32: astore_2
        33: aload_2
        34: invokevirtual #2                  // Method jni/superB.say:()V
        37: aload_2
        38: invokevirtual #18                 // Method jni/superB.print:()V
        41: aload_2
        42: invokevirtual #19                 // Method jni/superB.superDo:()V
        45: aload_1
        46: astore_3
        47: aload_3
        48: invokeinterface #20,  1           // InterfaceMethod jni/InterfaceA.say:()V  调用实例的类型都是接口类,即使该接口方法已经提供了默认实现,依然使用invokeinterface 
        53: aload_3
        54: invokeinterface #21,  1           // InterfaceMethod jni/InterfaceA.print:()V
        59: aload_1
        60: astore        4
        62: aload         4
        64: invokeinterface #22,  1           // InterfaceMethod jni/InterfaceB.interfaceDo:()V
        69: return

2、invokeinteface指令
该指令的实现参考OpenJDK8 hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp 2522行,源代码说明如下:

CASE(_invokeinterface): {
        //pc表示当前字节码指令的地址,get_native_u2返回从指定地址开始的2字节的数据,将其作为short读取
        //根据JVM规范,index表示运行时常量池的索引,指向一个接口方法的符号引用
        u2 index = Bytes::get_native_u2(pc+1);
 
        //获取常量池索引index处的值
        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        if (!cache->is_resolved((Bytecodes::Code)opcode)) {
         //如果接口方法未解析则解析
          CALL_VM(InterpreterRuntime::resolve_invoke(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        }
 
        istate->set_msg(call_method);
 
        //特殊场景下java.lang.Object的虚方法调用,
        if (cache->is_forced_virtual()) {
          Method* callee;
          //检查操作数栈的方法参数是否为空
          CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
          //如果是final方法
          if (cache->is_vfinal()) {
            //获取解析后的方法
            callee = cache->f2_as_vfinal_method();
            //final方法调用
            BI_PROFILE_UPDATE_FINALCALL();
          } else {
             //非final方法
            // Get receiver.
            int parms = cache->parameter_size();
            //获取执行方法调用的对象实例
            oop rcvr = STACK_OBJECT(-parms);
            //校验rcvr是否为空
            VERIFY_OOP(rcvr);
            //获取rcvr的实际类型Klass
            InstanceKlass* rcvrKlass = (InstanceKlass*)rcvr->klass();
            //获取虚方法表相同索引下的方法
            callee = (Method*) rcvrKlass->start_of_vtable()[ cache->f2_as_index()];
            //profile统计
            BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
          }
          //调用方法
          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
 
          istate->set_bcp_advance(5);
          //方法调用完成返回
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        }
 
        // this could definitely be cleaned up QQQ
        Method* callee;
        //获取接口方法
        Method *interface_method = cache->f2_as_interface_method();
        //获取接口类
        InstanceKlass* iclass = interface_method->method_holder();
 
        // get receiver
        int parms = cache->parameter_size();
        //获取方法调用对象实例
        oop rcvr = STACK_OBJECT(-parms);
        CHECK_NULL(rcvr);
        //获取方法调用对象实例的真实类型
        InstanceKlass* int2 = (InstanceKlass*) rcvr->klass();
 
        // Receiver subtype check against resolved interface klass (REFC).
        {
          //获取目标接口方法的resolved interface klass
          Klass* refc = cache->f1_as_klass();
          itableOffsetEntry* scan;
          //遍历int2实现的所有接口类,判断是否存在目标接口方法对应的resolved interface klass
          for (scan = (itableOffsetEntry*) int2->start_of_itable();
               scan->interface_klass() != NULL;
               scan++) {
            if (scan->interface_klass() == refc) {
              break;
            }
          }
          // int2没有实现目标resolved interface klass,抛出异常
          if (scan->interface_klass() == NULL) {
            VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
          }
        }
   
       //遍历int2实现的所有接口类,判断是否存在目标接口方法对应InstanceKlass     
        itableOffsetEntry* ki = (itableOffsetEntry*) int2->start_of_itable();
        int i;
        for ( i = 0 ; i < int2->itable_length() ; i++, ki++ ) {
          if (ki->interface_klass() == iclass) break;
        }
        // int2没有实现目标接口方法的InstanceKlass,抛出异常
        if (i == int2->itable_length()) {
          VM_JAVA_ERROR(vmSymbols::java_lang_IncompatibleClassChangeError(), "", note_no_trap);
        }
 
        //获取接口方法表的索引
        int mindex = interface_method->itable_index();
        
        //获取rcvr的接口方法表第一个元素
        itableMethodEntry* im = ki->first_method_entry(rcvr->klass());
        //获取接口方法表索引mindex处的方法
        callee = im[mindex].method();
        if (callee == NULL) {
          VM_JAVA_ERROR(vmSymbols::java_lang_AbstractMethodError(), "", note_no_trap);
        }
 
        //profile统计
        BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
 
        //执行方法调用
        istate->set_callee(callee);
        istate->set_callee_entry_point(callee->from_interpreted_entry());
 
        //字节码指针往后移动5个,_invokeinterface的指令长度是5
        istate->set_bcp_advance(5);
        UPDATE_PC_AND_RETURN(0); // I'll be back...
      }
3、invokevirtual,invokespecial,invokestatic
   invokevirtual,invokespecial,invokestatic三个指令的实现都是一样的,同样也是参考bytecodeInterpreter.cpp 2634行,源代码说明如下:

CASE(_invokevirtual):
      CASE(_invokespecial):
      CASE(_invokestatic): {
        //获取运行时常量池中目标方法的符号引用的索引
        u2 index = Bytes::get_native_u2(pc+1);
 
        //获取运行时常量池指定索引的符号引用解析对象
        ConstantPoolCacheEntry* cache = cp->entry_at(index);
        
        if (!cache->is_resolved((Bytecodes::Code)opcode)) {
          //如果未解析则解析符号引用
          CALL_VM(InterpreterRuntime::resolve_invoke(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->entry_at(index);
        }
 
        istate->set_msg(call_method);
        {
          Method* callee;
          //如果是invokevirtual指令
          if ((Bytecodes::Code)opcode == Bytecodes::_invokevirtual) {
            //判断方法调用实例对象是否为空
            CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            //如果是final方法
            if (cache->is_vfinal()) {
              //final方法不需走虚方法表分派,直接使用符号引用解析的结果
              callee = cache->f2_as_vfinal_method();
              // Profile final方法调用统计
              BI_PROFILE_UPDATE_FINALCALL();
            } else {
              // get receiver
              int parms = cache->parameter_size();
              //获取方法调用的实例对象
              oop rcvr = STACK_OBJECT(-parms);
              VERIFY_OOP(rcvr);
              //获取方法调用实例对象的实际klass
              InstanceKlass* rcvrKlass = (InstanceKlass*)rcvr->klass();
              //获取虚方法表中相同索引处的方法
              callee = (Method*) rcvrKlass->start_of_vtable()[ cache->f2_as_index()];
              //vitual 调用 profile统计
              BI_PROFILE_UPDATE_VIRTUALCALL(rcvr->klass());
            }
          } else {
            //invokespecial指令
            if ((Bytecodes::Code)opcode == Bytecodes::_invokespecial) {
              //判断方法调用实例对象是否为空
              CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
            }
            //invokespecial指令调用的方法不需要分派,直接使用符号引用解析的方法
            callee = cache->f1_as_method();
 
            // Profile统计
            BI_PROFILE_UPDATE_CALL();
          }
          //执行方法调用
          istate->set_callee(callee);
          istate->set_callee_entry_point(callee->from_interpreted_entry());
          //字节码指针往后移动3个,invokevirtual,invokespecial,invokestatic三个指令的指令长度都是3
          istate->set_bcp_advance(3);
          UPDATE_PC_AND_RETURN(0); // I'll be back...
        }
      }

从上述源码分析可知,invokevirtual,invokespecial,invokestatic,invokeinteface四个指令在找到了正确的执行方法后,就直接通过JVM解释器跳转到对应方法的执行了,跟JNI中方法调用有很大的不同

相关文章

  • JNI和字节码方法调用

    Java对象创建的本质就是按照对象的大小分配一块内存,然后完成属性的初始化。对象创建完了,接着干啥了?调用Java...

  • JNI技术简介

    JNI(Java Native Interface) 提供一种Java字节码调用C/C++的解决方案,JNI描述的...

  • Jvm基础知识下篇

    一、java执行引擎工作原理:方法调用 1.进行方法调用 Java语言的原子指令是字节码,java方法是对字节码的...

  • Android JNI学习手册

    一、JNI基础学习-JNI调用java原生方法 JNI调用java原生方法有四个重要的东西 一、class 类信息...

  • 要点提炼| 理解JVM之字节码执行引擎

    本篇将从概念模型的角度来介绍虚拟机的方法调用和字节码执行。 概述 运行时栈帧结构 方法调用 1.概述 a.有关虚拟...

  • 2022五一CRUD知识点合集

    字节码代理 字节码代理方法,会和skywalking 起冲突导致重写方法失败 -- 极海channel 调试页面抖...

  • Java的类加载机制

    一、JVM和类 运行Java:java 带有main方法的类名启动JVM,并加载字节码。 当调用java命令来运行...

  • 方法重载和方法重写的问题

    这是在网上搜到的 在Java虚拟机里面提供了5条方法调用字节码指令,分别如下: invokestatic:调用静态...

  • JNI方法数组和引用

    JNI方法 步骤:1.获取jclass2.获取方法ID;jmethodID3.jni调用java的方法 数组 步骤...

  • JNI方法动态注册

    简介 虚拟机在加载so库的时候,会调用JNI_OnLoad方法,所以可以在这JNI_OnLoad完成JNI方法动态...

网友评论

    本文标题:JNI和字节码方法调用

    本文链接:https://www.haomeiwen.com/subject/pxoybrtx.html