前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Method的invoke方法源码分析

Method的invoke方法源码分析

作者头像
itliusir
发布2018-05-21 17:05:30
1.4K0
发布2018-05-21 17:05:30
举报
文章被收录于专栏:刘君君刘君君

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

正文:

源码分析

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

代码语言:javascript
复制
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方法源码如下所示,就是一个接口:

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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方法执行的流程:

代码语言:javascript
复制
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参数监控类加载情况:

代码语言:javascript
复制
[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
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文:
  • 源码分析
  • 测试用例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档