专栏首页刘君君Method的invoke方法源码分析

Method的invoke方法源码分析

摘要:最近有使用到Method的invoke方法,于是就学习了下Method的invoke方法源码(暂未深入到native)

正文:

源码分析

首先看一下invoke方法的代码实现:

class AccessibleObject implements AnnotatedElement {
	boolean override;
	//访问权限
	public boolean isAccessible() {
       	return override;
   	}
}
//Method.class
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);
   }

可以看到在该方法第一步会先去判断AccessibleObject的override属性是否为true

  • 若为true则忽略访问权限的控制
  • 若为false则会去调用Reflection.quickCheckMemberAccess()判断是不是public,若不是则会使用Reflection.getCallerClass()获取调用此方法的class,然后校验其是否有权限
  • 最后会调用MethodAccessor的invoke()方法

MethodAccessor的invoke方法源码如下所示,就是一个接口:

public interface MethodAccessor {
   /** Matches specification in {@link java.lang.reflect.Method} */
   public Object invoke(Object obj, Object[] args)
       throws IllegalArgumentException, InvocationTargetException;
}

可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。 创建MethodAccessor实例的是ReflectionFactory。

sun.reflect.ReflectionFactory

public class ReflectionFactory {
   
    private static boolean initted = false;
    
    // ...

    //
    // "Inflation" mechanism. Loading bytecodes to implement
    // Method.invoke() and Constructor.newInstance() currently costs
    // 3-4x more than an invocation via native code for the first
    // invocation (though subsequent invocations have been benchmarked
    // to be over 20x faster). Unfortunately this cost increases
    // startup time for certain applications that use reflection
    // intensively (but only once per class) to bootstrap themselves.
    // To avoid this penalty we reuse the existing JVM entry points
    // for the first few invocations of Methods and Constructors and
    // then switch to the bytecode-based implementations.
    //
    // Package-private to be accessible to NativeMethodAccessorImpl
    // and NativeConstructorAccessorImpl
    private static boolean noInflation        = false;
    private static int     inflationThreshold = 15;
    
    // ...
    
    /** We have to defer full initialization of this class until after
        the static initializer is run since java.lang.reflect.Method's
        static initializer (more properly, that for
        java.lang.reflect.AccessibleObject) causes this class's to be
        run, before the system properties are set up. */
    private static void checkInitted() {
        if (initted) return;
        AccessController.doPrivileged(new PrivilegedAction() {
                public Object 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 (RuntimeException) 
                                new RuntimeException("Unable to parse property sun.reflect.inflationThreshold").
                                    initCause(e);
                        }
                    }

                    initted = true;
                    return null;
                }
            });
    }
    
    // ...
    
    public MethodAccessor newMethodAccessor(Method method) {
        checkInitted();

        if (noInflation) {
            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;
        }
    }
}

可以看到,实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。

这个需要注意的是inflationThreshold的值是15,也就是说前15次是使用的native版本,之后使用的是java版本,具体实现可以往下看。

为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。 (Sun的JDK是从1.4系开始采用这种优化的)

可以在启动命令里加上-Dsun.reflect.noInflation=true,就会RefactionFactorynoInflation属性就变成true了,这样不用等到15调用后,程序一开始就会用java版的MethodAccessor了

可以在上段代码newMethodAccessor()方法看到DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);

sun.reflect.DelegatingMethodAccessorImpl

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }    

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}

这个类是方便在native与Java版的MethodAccessor之间实现切换。

sun.reflect.NativeMethodAccessorImpl

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }    

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        if (++numInvocations > ReflectionFactory.inflationThreshold()) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }
        
        return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}

可以看到在每次调用invoke方法时候会++numInvocations,inflationThreshold的值是15,该块就是上文所说的native版本和java版本的切换实现部分。当numInvocations超过inflationThreshold的值调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

测试用例

可以使用demo测试invoke方法执行的流程:

public class A {
	public void foo(String name) {
		System.out.println("Hello," + name);
	}
}

//Test
public class TestClassLoad {
	public static void main(String[] args) throws Exception {
			Class<?> clz = Class.forName("testinvoke.A");
			Object o = clz.newInstance();
			Method m = clz.getMethod("foo", String.class);
			for (int i = 0; i < 16; i++) {
				m.invoke(o, Integer.toString(i));
			}
		}
	}
}

可以在运行的时候使用-XX:+TraceClassLoading参数监控类加载情况:

[Loaded testinvoke.A from file:/C:/Users/itliusir/git/test/Test/bin/]
[Loaded sun.reflect.NativeMethodAccessorImpl from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from D:\jdk8\jre\lib\rt.jar]
Hello,0
Hello,1
Hello,2
Hello,3
Hello,4
Hello,5
Hello,6
Hello,7
Hello,8
Hello,9
Hello,10
Hello,11
Hello,12
Hello,13
Hello,14
[Loaded sun.reflect.ClassFileConstants from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.Label from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from D:\jdk8\jre\lib\rt.jar]
[Loaded java.util.A

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JVM Monitor

    其中 load average 代表的是cpu的平均负载,三个数字分别代表1分钟、5分钟、15分钟内cpu的平均负载。 负荷的大小跟cpu个数以及当前负荷...

    itliusir
  • 线程的实现与分析

    线程是操作系统调度的最小单位,实现线程有三种方式,而 Java Thread 采用的是 内核线程实现

    itliusir
  • Validator 使用总结

    itliusir
  • 中间件漏洞检测框架利器F-MiddlewareScan

    介绍 纯python编写的轻量级中间件漏洞检测框架,实现针对中间件的自动化检测,端口探测->中间件识别->漏洞检测->获取webshell 漏洞检测脚本以插件形...

    企鹅号小编
  • Jconsole-java监视和管理控制台的使用

    Jconsole 监控java程序时,在启动java程序时,加上以下选项就可以进行远程监控:

    yawn
  • Vue2.0实现在线商城

    大象无痕
  • 匈牙利算法(二分图最大匹配问题)

    匈牙利算法用于求解无权二分图(unweighted bipartite graph)的最大匹配(maximum matching)问题

    mathor
  • 腾讯面试题之Java实现莱文斯坦(相似度)算法

    使用Levenshtein(莱文斯坦)编辑距离来实现相似度算法 所谓Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,操作包...

    week
  • DevOps将在5个领域影响云计算

    根据调研机构Allied Market Research公司的预计,到2023年,DevOps市场规模将达到94亿美元。而在2020年,云计算服务规模预计将增长...

    静一
  • 【动态规划】01背包问题

    前面用动态规划解决了正则表达式的问题,感觉还是不过瘾,总觉得对于动态规划的理解还没有到位,所以趁热打铁,继续研究几个动态规划的经典问题,希望能够借此加深对动态规...

    用户1370629

扫码关注云+社区

领取腾讯云代金券