深入理解Java反射中的invoke方法

什么是反射

反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

Oracle 官方对反射的解释是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.  The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

重点:是运行时而不是编译时

反射的主要用途

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,编译器就会自动列出它的属性或方法,这里就会用到反射,当然也有的用到了语法树。

在 Web 开发中,我们经常能够接触到各种可配置的通用框架。为了保证框架的可扩展性,它们往 往借助 Java 的反射机制,根据配置文件来加载不同的类。举例来说,Spring 框架的依赖反转 (IoC),便是依赖于反射机制。

反射invoke实现原理

invoke方法用来在运行时动态地调用某个实例的方法

它的实现代码如下:

@CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }
 

1.权限检查

通过代码我们可以看到,首先invoke方法会检查 AccessibleObject 的override属性的值。而 AccessibleObject 类是实现了AnnotatedElement,它是Field、Method和Constructor对象的基类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method或Constructor对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。

override的值默认是false,表示需要权限调用规则。我们常使用的 setAccessible 方法就是将其设置为true,从而忽略权限规则,调用方法时无需检查权限。

继续往下看,当其需要权限调用则走 Reflection.quickCheckMemberAccess ,检查方法是否为public,如果是的话跳出本步。如果不是public方法,那么用 Reflection.getCallerClass() 方法获取调用这个方法的Class对象

public static native Class<?> getCallerClass();
 

这是一个native方法,我们从openJDK源码中去找它的JNI入口(Reflection.c)

//JNIEnv: java本地方法调用的上下文,
//jclass: 在java中的Class实例
JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__
(JNIEnv *env, jclass unused)
{
    return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}
 

具体实现在hotspot/src/share/vm/prims/jvm.cpp

获取了这个Class对象caller后用 checkAccess 方法做一次快速的权限校验

volatile Object securityCheckCache;
 
    void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
        throws IllegalAccessException
    {
        if (caller == clazz) {  // quick check
            return;             // ACCESS IS OK
        }
        Object cache = securityCheckCache;  // read volatile
        Class<?> targetClass = clazz;
        if (obj != null
            && Modifier.isProtected(modifiers)
            && ((targetClass = obj.getClass()) != clazz)) {
            // Must match a 2-list of { caller, targetClass }.
            if (cache instanceof Class[]) {
                Class<?>[] cache2 = (Class<?>[]) cache;
                if (cache2[1] == targetClass &&
                    cache2[0] == caller) {
                    return;     // ACCESS IS OK
                }
                // (Test cache[1] first since range check for [1]
                // subsumes range check for [0].)
            }
        } else if (cache == caller) {
            // Non-protected case (or obj.class == this.clazz).
            return;             // ACCESS IS OK
        }
 
        // If no return, fall through to the slow path.
        slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
    }
 

这里主要是进行一些基本的权限检查,以及使用缓存机制。

2.调用MethodAccessor的invoke方法

我们主要关注这个流程中的操作

MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
 
// NOTE that there is no synchronization used here. It is correct
// (though not efficient) to generate more than one MethodAccessor
// for a given Method. However, avoiding synchronization will
// probably make the implementation more scalable.
private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }
    return tmp;
}
 

首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。

而当第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建,所以通过 reflectionFactory 创建MethodAccessor并更新给root,然后调用 MethodAccessor.invoke() 完成反射调用。

可以看到invoke方法实际是委派给了MethodAccessor类型的ma对象来处理。MethodAccessor是一个接口,有两个实现类。一个委派实现( DelegatingMethodAccessorImpl ),一个本地实现( NativeMethodAccessorImpl )。这里调用的委派实现主要是为了在本地实现和动态实现之间做切换。考虑到许多反射调用仅会执行一次,Java虚拟机设置了一个阈值15(是从0开始计算,>15),当某个反射调用的调用次数<=15 时,采用本地实现;当大于15时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现。这个过程我们称之为Infation。

这里我们先通过demo测试看一下委派实现对本地实现和动态实现的切换,再具体分析其两种invoke的底层实现。

public class HowReflect {
    public static void targetMethod(int i) {
        // 打印调用栈
        new Exception("版本 " + i)
                .printStackTrace();
    }
 
    public static void main(String[] args) throws Exception {
        Class<?> howReflect = Class.forName("cn.rui0.reflect.HowReflect");
        Method method = howReflect.getMethod("targetMethod", int.class);
        // 执行方法
        method.invoke(null, 0);
    }
}
 

运行结果

java.lang.Exception: 版本 0
    at cn.rui0.reflect.HowReflect.test(HowReflect.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    // 4 委派实现又委派给了本地实现
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    // 3 生成委派实现
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    // 2 反射调用方法
    at java.lang.reflect.Method.invoke(Method.java:498)
    // 1 执行main方法
    at cn.rui0.reflect.HowReflect.main(HowReflect.java:19)
 

可以看到如果第一次invoke某方法,它实际调用的是本地实现。

接着我们修改一下代码

public class HowReflect {
    public static void targetMethod(int i) {
        // 打印调用栈
        new Exception("版本 " + i)
                .printStackTrace();
    }
 
    public static void main(String[] args) throws Exception {
        Class<?> howReflect = Class.forName("cn.rui0.reflect.HowReflect");
        Method method = howReflect.getMethod("targetMethod", int.class);
        for (int i = 0; i < 20; i++) {
            // 执行方法
            method.invoke(null, i);
        }
    }
}
 

结果

可以看到版本15后切换为动态实现

下面我们来从源码层面分析上述过程

因为 methodAccessor 实例由 reflectionFactory 对象操控生成,所以我们先来看一下 ReflectionFactory 类的源码

inflationThreshold就是我们之前说的阈值

public MethodAccessor newMethodAccessor(Method method) {
        checkInitted();
        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
            NativeMethodAccessorImpl acc =
                new NativeMethodAccessorImpl(method);
            DelegatingMethodAccessorImpl res =
                new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }
private static void checkInitted() {
        if (initted) return;
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                    // Tests to ensure the system properties table is fully
                    // initialized. This is needed because reflection code is
                    // called very early in the initialization process (before
                    // command-line arguments have been parsed and therefore
                    // these user-settable properties installed.) We assume that
                    // if System.out is non-null then the System class has been
                    // fully initialized and that the bulk of the startup code
                    // has been run.
                    if (System.out == null) {
                        // java.lang.System not yet fully initialized
                        return null;
                    }
                    String val = System.getProperty("sun.reflect.noInflation");
                    if (val != null && val.equals("true")) {
                        noInflation = true;
                    }
                    val = System.getProperty("sun.reflect.inflationThreshold");
                    if (val != null) {
                        try {
                            inflationThreshold = Integer.parseInt(val);
                        } catch (NumberFormatException e) {
                            throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
                        }
                    }
                    initted = true;
                    return null;
                }
            });
    }
 

可以看到 newMethodAccessor 根据不同的条件分别选择了本地实现和动态实现,动态实现和本地实现相比,其运行效率要快上20倍。这是因为动态实现无需经过Java到C++再到Java的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上3到4倍。

为了尽可能地减少性能损耗,HotSpot JDK采用我们之前提到的“inflation”的技巧,也就是让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。

实际上如果你看过fastjson源码,就能发现它为了优化性能在json转对象时也加入了这种动态生成的方法,主要也通过使用asm完成。

本地实现

一开始(native版)会生产 NativeMethodAccessorImplDelegatingMethodAccessorImpl 两个对象。 DelegatingMethodAccessorImpl 是一个中间层,是为了在native版与Java版的MethodAccessor之间进行切换。

我们主要关注 NativeMethodAccessorImpl

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;
 
    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }
 
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }
 
        return invoke0(this.method, var1, var2);
    }
 
    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }
 
    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
 
 

每次调用其invoke时会做一个累加,判断是否到达阙值,如果没有则调用native的invoke0方法,当超过时则调用 MethodAccessorGenerator.generateMethod() ,并将其设置到 DelegatingMethodAccessorImpl 的delegate,这样下次就会直接调用到动态实现的位置。

下面我们从jvm底层分析这里的native实现invoke0

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
 

JVM_InvokeMethod在这里 hotspot/src/share/vm/prims/jvm.cpp 实现

其关键是

oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
 

跟进hotspot/src/share/vm/runtime/reflection.cpp

oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
  oop mirror             = java_lang_reflect_Method::clazz(method_mirror);
  int slot               = java_lang_reflect_Method::slot(method_mirror);
  bool override          = java_lang_reflect_Method::override(method_mirror) != 0;
  objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));
 
  oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
  BasicType rtype;
  if (java_lang_Class::is_primitive(return_type_mirror)) {
    rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
  } else {
    rtype = T_OBJECT;
  }
 
  instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));
  Method* m = klass->method_with_idnum(slot);
  if (m == NULL) {
    THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
  }
  methodHandle method(THREAD, m);
 
  return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}
 

在这之前先对JVM的oop-klass做一个简单的介绍:

1.oop(ordinary object pointer):类型其实是 oopDesc*,在 Java 程序运行的过程中,每创建一个新的对象,在 JVM 内部就会相应地创建一个对应类型的 oop 对象。实际上就是普通对象指针,用于描述对象的实例信息。各种 oop 类的共同基类为 oopDesc 类。

一个OOP对象包含以下几个部分:

  • 对象头 (header)
    • Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等
    • 元数据指针,即指向方法区的instanceKlass实例
  • 实例数据。存储的是真正有效数据,如各种字段内容,各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),相同宽度的字段总是被分配到一起,便于之后取数据。父类定义的变量会出现在子类定义的变量的前面。
  • 对齐填充。仅仅起到占位符的作用,并非必须。

2.klass:Java类在JVM中的表示,是对Java类的描述。简单的说是Java类在HotSpot中的c++对等体,用来描述Java类。klass是什么时候创建的呢?一般jvm在加载class文件时,会在方法区创建instanceKlass,表示其元数据,包括常量池、字段、方法等。

网上的oop-klass模型示例图很多,我随便找了一个:

JVM就是用这种方式,将一个对象的数据和对象模型进行分离。普遍意义上来说,我们说持有一个对象的引用,指的是图中的handle(存放在栈区),它是oop(存放在堆区)的一个封装。

我们再看代码 Reflection::invoke_method() 中接受的method_mirror(oop)就是我们要反射调用的方法。然后代码调用了 Reflection::invoke()

跟进之后最终到 JavaCalls::call() 执行

位于hotspot/src/share/vm/runtime/javaCalls.cpp

void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
  // Check if we need to wrap a potential OS exception handler around thread
  // This is used for e.g. Win32 structured exception handlers
  assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
  // Need to wrap each and everytime, since there might be native code down the
  // stack that has installed its own exception handlers
  os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}
 

最后的 os_exception_wrapper 其实就是调用了 call_help ,也就是说本地实现的反射最终的方法执行是通过 JavaCalls::call_helper 方法来完成的

void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
    ......
      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        args->parameters(),
        args->size_of_parameters(),
        CHECK
      );
    ......
}
 

上面代码中 StubRoutines::call_stub() 返回的是一个函数指针,在执行上面的 call_stub() 时,会先将参数先压入堆栈。

这个函数指针指向什么地方呢,这是和机器类型有关的,以X86-32为例,看hotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp里面

下面大家会看到很多类似汇编指令的代码,其实这些不是指令,而是一个个用来生成汇编指令的方法。JVM是通过MacroAssembler来生成指令的。我会将具体的执行过程通过注释的方式插入到代码中

address generate_call_stub(address& return_address) {
    StubCodeMark mark(this, "StubRoutines", "call_stub");
    //汇编器会将生成的例程在内存中线性排列。所以取当前汇编器生成的上个例程最后一行汇编指令的地址,用来作为即将生成的新例程的首地址
    address start = __ pc();
 
    // stub code parameters / addresses
    assert(frame::entry_frame_call_wrapper_offset == 2, "adjust this code");
    bool  sse_save = false;
    const Address rsp_after_call(rbp, -4 * wordSize); // same as in generate_catch_exception()!
    const int     locals_count_in_bytes  (4*wordSize);
 
    //定义一些变量,用于保存一些调用方的信息,这四个参数放在被调用者堆栈中,即call_stub例程堆栈中,所以相对于call_stub例程的栈基址(rbp)为负数。(栈是向下增长),后面会用到这四个变量。
    const Address mxcsr_save    (rbp, -4 * wordSize);
    const Address saved_rbx     (rbp, -3 * wordSize);
    const Address saved_rsi     (rbp, -2 * wordSize);
    const Address saved_rdi     (rbp, -1 * wordSize);
    //传参,放在调用方堆栈中,所以相对call_stub例程的栈基址为正数,可以理解为调用方在调用call_stub例程之前,会将传参都放在自己的堆栈中,这样call_stub例程中就可以直接基于栈基址进行偏移取用了。
    const Address result        (rbp,  3 * wordSize);
    const Address result_type   (rbp,  4 * wordSize);
    const Address method        (rbp,  5 * wordSize);
    const Address entry_point   (rbp,  6 * wordSize);
    const Address parameters    (rbp,  7 * wordSize);
    const Address parameter_size(rbp,  8 * wordSize);
    const Address thread        (rbp,  9 * wordSize); // same as in     generate_catch_exception()!
    sse_save =  UseSSE > 0;
 
    //enter()对应的方法如下,用来保存调用方栈基址,并将call_stub栈基址更新为当前栈顶地址,c语言编译器其实在调用方法前都会插入这件事,这里JVM相对于借用了这种思想。
 ---------------------------------------------
|        void MacroAssembler::enter() {       |
|            push(rbp);                       |
|            mov(rbp, rsp);                   |
|       }                                     |
 ---------------------------------------------
    __ enter();
 
    //接下来计算并分配call_stub堆栈所需栈大小。
    //先将参数数量放入rcx寄存器。
    __ movptr(rcx, parameter_size);              // parameter counter
    //shl用于左移,这里将rcx中的值左移了Interpreter::logStackElementSize位,在64位平台,logStackElementSize=3;在32位平台,logStackElementSize=2;所以在64位平台上,rcx = rcx * 8, 即每个参数占用8字节;32位平台rcx = rcx *4 ,即每个参数占4个字节。
    __ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
 
    // locals_count_in_bytes 在上面有定义:const int     locals_count_in_bytes  (4*wordSize);这四个字节其实就是上面用来保存调用方信息所占空间。
    __ addptr(rcx, locals_count_in_bytes);       // reserve space for register saves
 
    //rcx现在保存了计算好的所需栈空间,将保存栈顶地址的寄存器rsp减去rcx,即向下扩展栈。
    __ subptr(rsp, rcx);
 
    //引用《揭秘Java虚拟机》:为了加速内存寻址和回收,物理机器在分配堆栈空间时都会进行内存对齐,JVM也借用了这个思想。JVM中是按照两个字节,即16位进行对齐的:const int StackAlignmentInBytes = (2*wordSize);
    __ andptr(rsp, -(StackAlignmentInBytes));    // Align stack
 
    //将调用方的一些信息,保存到栈中分配的地址处,最后会再次还原到寄存器中
    __ movtr(saved_rdi, rdi);
    __ movptr(saved_rsi, rsi);
    __ movptr(saved_rbx, rbx);
   ......
   ......
    //接下来就要进行参数压栈了;
    Label parameters_done;
    //检查参数数量是否为0,为0则直接跳到标号parameters_done处。
    __ movl(rcx, parameter_size);  // parameter counter
    __ testl(rcx, rcx);
    __ jcc(Assembler::zero, parameters_done);
 
    Label loop
    //将参数首地址放到寄存器rdx中,并将rbx置0;
    __ movptr(rdx, parameters);          // parameter pointer
    __ xorptr(rbx, rbx);
 
    //标号loop处
    __ BIND(loop);
 
    //此处开始循环;从最后一个参数倒序往前进行参数压栈,初始时,rcx = parameter_size;要注意,这里的参数是指java方法所需的参数,而不是call_stub例程所需参数!
    //将(rdx + rcx * stackElementScale()- wordSize )移到 rax 中,(rdx + rcx * stackElementScale()- wordSize )指向了要压栈的参数。
    __ movptr(rax, Address(rdx, rcx, Interpreter::stackElementScale(), -wordSize));
    //再从rax中转移到(rsp + rbx * stackElementScale()) 处,expr_offset_in_bytes(0) = 0;这里是基于栈顶地址进行偏移寻址的,最后一个参数会被压到栈顶处。第一个参数会被压到rsp + (parameter_size-1)* stackElementScale()处。
    __ movptr(Address(rsp, rbx, Interpreter::stackElementScale(),
                Interpreter::expr_offset_in_bytes(0)), rax);          // store parameter
    //更新rbx
    __ increment(rbx);
    //自减rcx,当rcx不为0时,继续跳往loop处循环执行。
    __ decrement(rcx);
    __ jcc(Assembler::notZero, loop);
 
    //标号parameters_done处
    __ BIND(parameters_done);
 
   //接下来要开始调用Java方法了。
   //将调用java方法的entry_point例程所需的一些参数保存到寄存器中
    __ movptr(rbx, method);           // get Method*
    __ movptr(rax, entry_point);      // get entry_point
    __ mov(rsi, rsp);                 // set sender sp
   //跳往entry_point例程执行
    __ call(rax);
 
    ......
}
 

可以简化来看

address generate_call_stub(address& return_address) {
    // 保存重要的参数比如解释器入口点,Java方法返回地址等
    // 将Java方法的参数压入栈
 
    // 调用Java方法
    __ movptr(rbx, method);           // 将Method*指针存放到rbx
    __ movptr(rax, entry_point);      // 将解释器入口存放到rax
    __ mov(rsi, rsp);                 // 将当前栈顶存放到rsi
    __ call(rax);                     // 进入解释器入口!
 
    // 处理Java方法返回值
    // 弹出Java参数
    // 返回
    return start;
  }
 

它实际首先建立了一个栈帧,这个栈帧里面保存了一些重要的数据,再把Java方法的参数压入栈。当这一步完成,栈帧变成了这个样子:

代码中最终会走call rax相当于 call entry_point ,entry_point例程和call_stub例程一样,都是用汇编写的来执行Java方法的工具。这个entry_point即解释器入口点,最终的方法执行过程其实是在这里面的。

这个entry_point从何而来,从 method->from_interpreted_entry() ,从 methodOopDesc::link_method 中获取 address entry = Interpreter::entry_for_method(h_method) ,也就是说如果不用jit的,直接调用解析器的入口,由解释器再进行操作。

动态实现

主要看 MethodAccessorGenerator 类generate方法

实际就是通过asm来生成对应的java字节码来调用方法

我们可以简单来分析一下关键代码

可见动态生成的类名为GeneratedMethodAccessor+编号

我们找到对应的类 来看看它写入字节码的几个关键操作

可见代码生成对应的字节码,这段代码主要是需要调用的方法signature等信息放到常量池里,用于后续code区调用

后续生成code区字节码的方法是 emitInvoke

    private void emitInvoke() {
        if(this.parameterTypes.length > '\uffff') {
            throw new InternalError("Can't handle more than 65535 parameters");
        } else {
            ClassFileAssembler var1 = new ClassFileAssembler();
            if(this.isConstructor) {
                var1.setMaxLocals(2);
            } else {
                var1.setMaxLocals(3);
            }
 
            short var2 = 0;
            Label var3;
            if(this.isConstructor) {
                var1.opc_new(this.targetClass);
                var1.opc_dup();
            } else {
                if(isPrimitive(this.returnType)) {
                    var1.opc_new(this.indexForPrimitiveType(this.returnType));
                    var1.opc_dup();
                }
 
                if(!this.isStatic()) {
                    var1.opc_aload_1();
                    var3 = new Label();
                    var1.opc_ifnonnull(var3);
                    var1.opc_new(this.nullPointerClass);
                    var1.opc_dup();
                    var1.opc_invokespecial(this.nullPointerCtorIdx, 0, 0);
                    var1.opc_athrow();
                    var3.bind();
                    var2 = var1.getLength();
                    var1.opc_aload_1();
                    var1.opc_checkcast(this.targetClass);
                }
            }
 
            var3 = new Label();
            if(this.parameterTypes.length == 0) {
                if(this.isConstructor) {
                    var1.opc_aload_1();
                } else {
                    var1.opc_aload_2();
                }
 
                var1.opc_ifnull(var3);
            }
 
            if(this.isConstructor) {
                var1.opc_aload_1();
            } else {
                var1.opc_aload_2();
            }
 
            var1.opc_arraylength();
            var1.opc_sipush((short)this.parameterTypes.length);
            var1.opc_if_icmpeq(var3);
            var1.opc_new(this.illegalArgumentClass);
            var1.opc_dup();
            var1.opc_invokespecial(this.illegalArgumentCtorIdx, 0, 0);
            var1.opc_athrow();
            var3.bind();
            short var4 = this.nonPrimitiveParametersBaseIdx;
            Label var5 = null;
            byte var6 = 1;
 
            for(int var7 = 0; var7 < this.parameterTypes.length; ++var7) {
                Class var8 = this.parameterTypes[var7];
                var6 += (byte)this.typeSizeInStackSlots(var8);
                if(var5 != null) {
                    var5.bind();
                    var5 = null;
                }
 
                if(this.isConstructor) {
                    var1.opc_aload_1();
                } else {
                    var1.opc_aload_2();
                }
 
                var1.opc_sipush((short)var7);
                var1.opc_aaload();
                if(!isPrimitive(var8)) {
                    var1.opc_checkcast(var4);
                    var4 = add(var4, 2);
                } else {
                    if(this.isConstructor) {
                        var1.opc_astore_2();
                    } else {
                        var1.opc_astore_3();
                    }
 
                    Label var9 = null;
                    var5 = new Label();
 
                    for(int var10 = 0; var10 < primitiveTypes.length; ++var10) {
                        Class var11 = primitiveTypes[var10];
                        if(canWidenTo(var11, var8)) {
                            if(var9 != null) {
                                var9.bind();
                            }
 
                            if(this.isConstructor) {
                                var1.opc_aload_2();
                            } else {
                                var1.opc_aload_3();
                            }
 
                            var1.opc_instanceof(this.indexForPrimitiveType(var11));
                            var9 = new Label();
                            var1.opc_ifeq(var9);
                            if(this.isConstructor) {
                                var1.opc_aload_2();
                            } else {
                                var1.opc_aload_3();
                            }
 
                            var1.opc_checkcast(this.indexForPrimitiveType(var11));
                            var1.opc_invokevirtual(this.unboxingMethodForPrimitiveType(var11), 0, this.typeSizeInStackSlots(var11));
                            emitWideningBytecodeForPrimitiveConversion(var1, var11, var8);
                            var1.opc_goto(var5);
                        }
                    }
 
                    if(var9 == null) {
                        throw new InternalError("Must have found at least identity conversion");
                    }
 
                    var9.bind();
                    var1.opc_new(this.illegalArgumentClass);
                    var1.opc_dup();
                    var1.opc_invokespecial(this.illegalArgumentCtorIdx, 0, 0);
                    var1.opc_athrow();
                }
            }
 
            if(var5 != null) {
                var5.bind();
            }
 
            short var12 = var1.getLength();
            if(this.isConstructor) {
                var1.opc_invokespecial(this.targetMethodRef, var6, 0);
            } else if(this.isStatic()) {
                var1.opc_invokestatic(this.targetMethodRef, var6, this.typeSizeInStackSlots(this.returnType));
            } else if(this.isInterface()) {
                if(this.isPrivate()) {
                    var1.opc_invokespecial(this.targetMethodRef, var6, 0);
                } else {
                    var1.opc_invokeinterface(this.targetMethodRef, var6, var6, this.typeSizeInStackSlots(this.returnType));
                }
            } else {
                var1.opc_invokevirtual(this.targetMethodRef, var6, this.typeSizeInStackSlots(this.returnType));
            }
 
            short var13 = var1.getLength();
            if(!this.isConstructor) {
                if(isPrimitive(this.returnType)) {
                    var1.opc_invokespecial(this.ctorIndexForPrimitiveType(this.returnType), this.typeSizeInStackSlots(this.returnType), 0);
                } else if(this.returnType == Void.TYPE) {
                    var1.opc_aconst_null();
                }
            }
 
            var1.opc_areturn();
            short var14 = var1.getLength();
            var1.setStack(1);
            var1.opc_invokespecial(this.toStringIdx, 0, 1);
            var1.opc_new(this.illegalArgumentClass);
            var1.opc_dup_x1();
            var1.opc_swap();
            var1.opc_invokespecial(this.illegalArgumentStringCtorIdx, 1, 0);
            var1.opc_athrow();
            short var15 = var1.getLength();
            var1.setStack(1);
            var1.opc_new(this.invocationTargetClass);
            var1.opc_dup_x1();
            var1.opc_swap();
            var1.opc_invokespecial(this.invocationTargetCtorIdx, 1, 0);
            var1.opc_athrow();
            ClassFileAssembler var16 = new ClassFileAssembler();
            var16.emitShort(var2);
            var16.emitShort(var12);
            var16.emitShort(var14);
            var16.emitShort(this.classCastClass);
            var16.emitShort(var2);
            var16.emitShort(var12);
            var16.emitShort(var14);
            var16.emitShort(this.nullPointerClass);
            var16.emitShort(var12);
            var16.emitShort(var13);
            var16.emitShort(var15);
            var16.emitShort(this.throwableClass);
            this.emitMethod(this.invokeIdx, var1.getMaxLocals(), var1, var16, new short[]{this.invocationTargetClass});
        }
    }
 

可以看到生成的字节码,里面引入了HowReflect.targetMethod

最后可以整体看一下字节码反编译成的Java文件,即可以理解为动态实现就是通过生成这样一个java类去调用指定方法。

package sun.reflect;
 
import cn.rui0.reflect.HowReflect;
import java.lang.reflect.InvocationTargetException;
 
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
    public Object invoke(Object obj, Object[] objArr) throws InvocationTargetException {
        int intValue;
        try {
            if (objArr.length != 1) {
                throw new IllegalArgumentException();
            }
            Byte b = objArr[0];
            if (b instanceof Byte) {
                intValue = b.byteValue();
            } else if (b instanceof Character) {
                intValue = ((Character) b).charValue();
            } else if (b instanceof Short) {
                intValue = ((Short) b).shortValue();
            } else if (b instanceof Integer) {
                intValue = ((Integer) b).intValue();
            } else {
                throw new IllegalArgumentException();
            }
            try {
                HowReflect.targetMethod(intValue);
                return null;
            } catch (Throwable th) {
                throw new InvocationTargetException(th);
            }
        } catch (ClassCastException | NullPointerException e) {
            throw new IllegalArgumentException(GeneratedMethodAccessor1.super.toString());
        }
    }
}
 

参考

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章