按其名称调用方法,该方法存储在没有反射API的字符串中



我知道,使用反射API,我们可以通过存储在字符串中的方法名称调用方法。

但是,反射API不能用于高性能应用程序。在我的应用程序中,方法将以非常高的速率被调用。所以,我不能使用反射API。

那么,反射API的替代方案是什么?

我做了研究,发现可以使用cglib和其他代码生成库。

但是,我没有找到任何例子来调用方法的名称存储在一个字符串。

一个反射选项的例子也会很好。

更新:实际上,我正在实现一些主从通信API。其中,从服务器将远程调用主方法。而且,方法调用将以非常高的速率进行(大约每秒50次方法调用)。因为,主服务器不断地轮询从服务器以获取任何响应。那么,我应该在如此高的调用率下尝试反射吗?

这就是的反射。在排除它之前,我建议尝试一下,看看在过去几年的任何JVM上,您是否真的看到了与它相关的任何性能问题。我怀疑你不会。

你唯一的其他实物期权 (实际上,有cglib;是一个让人们调用的方法,传入要调用的方法的名称,然后分派给该方法(例如,使用一个大的switch,或分派表,或类似的)。例如:

public Object callMethod(String methodName, Object[] args) {
    switch (methodName) { // Using strings in `switch` requires a recent version of Java
        case "foo":
            return this.foo(args[0]);
        case "bar":
            this.bar(args[0], args[1]);
            return null;
        // ...and so on...
        default:
            throw new AppropriateException();
    }
}

Cglib附带一个名为FastMethod的类。该类的目的是通过使用非反射接口调用给定的方法。为此,FastMethod实现接口并生成用于调用指定方法的字节码,从而避免了被认为代价高昂的反射调用。

然而,这里有两个理由说明为什么你不应该使用这个类。Cglib是很久以前写的。在那些日子里,反射仍然比今天更昂贵。然而,现代jvm知道一个叫做膨胀的概念。默认情况下,JVM将在第15次反射调用之后为调用方法生成字节代码。这正是cglib为您提供的显式功能。

此外,最昂贵的不是反射调用,而是查找。您仍然需要将方法命名为FastMethod。因此,即使使用cglib,也无法避免这些成本。

因此,我建议您依赖反射,直到您真正确定这是性能瓶颈。至少,使用JMH这样的工具来证明这样的实现是合理的。另外,要考虑到类占用的元/元空间可能会给用户带来麻烦。

从Java 1.7开始,有一种通过MethodHandle从方法名调用方法的新方法,它比反射快得多。

我在一台蹩脚的笔记本电脑上做了一些基准测试:

  1. 反射每秒可以执行大约400万次调用
  2. MethodHandle每秒可以做大约1.4亿次调用

都是可以接受的性能,但是平均调用时间只有7纳秒,使用MethodHandle是非常好的。

如果您根据名称在HashMap中存储了对MethodHandle的引用,则可以在后续调用中重用它。

认为Reflection很慢是一个常见的误解。那是在Java 1.3左右的时代。

但是在现代Java中,Reflection确实得到了很好的优化。它在底层使用动态字节码生成。此外,JVM可以将这样的调用直接内联到调用者中,因此使用Reflection调用方法几乎和直接调用一样快。

这里的其他注释建议使用cglib的FastMethod。事实上,它并不比Reflection快多少。下面是一个用著名的JMH框架编写的基准测试:

@State(Scope.Benchmark)
public class MethodInvoke {
    Method method;
    FastMethod fastMethod;
    @Setup
    public void init() throws NoSuchMethodException {
        method = getClass().getMethod("foo", int.class, long.class);
        method.setAccessible(true);
        fastMethod = FastClass.create(getClass()).getMethod("foo", new Class[] { int.class, long.class });
    }
    @GenerateMicroBenchmark
    public long fastMethod() throws Exception {
        return (Long) fastMethod.invoke(this, new Object[] {2, 3L});
    }
    @GenerateMicroBenchmark
    public long reflection() throws Exception {
        return (Long) method.invoke(this, 2, 3L);
    }
    public long foo(int a, long b) {
        return a + b;
    }
}

Java 7u51(64位)的结果:

Benchmark                     Mode   Samples         Mean   Mean error    Units
b.MethodInvoke.fastMethod    thrpt         5    79248,583     3978,941   ops/ms
b.MethodInvoke.reflection    thrpt         5    76975,414     2844,730   ops/ms

你看,FastMethod.invoke只比Method.invoke快3%,但与反射相反,FastMethod没有执行适当的参数验证等。

通常,方法的内省调用分为两个阶段:首先需要找到要调用的目标方法,然后在目标实例上调用该方法,为其提供参数。

在这个过程中,最昂贵的操作是在类中定位目标方法(例如Method method = TargetClass.getMethod(Class[] signature ...)),一旦你获得了method对象,调用对象上的方法,如:method.invoke(targetObj, param,...)是一个轻量级操作,只是比直接方法调用稍微昂贵一些。

为了演示这一点,我只是做了一个快速的&将三个方法与以下结果进行比较(您需要相对比较它们):

  • 每次反射方法+调用:167ms
  • 类+调用的第一个缓存方法:36 ms
  • 直接调用:17ms

请注意,自省具有固定的性能成本,因此该方法执行的计算越多,这些数字就越接近。

我在以前的项目中使用了这种方法缓存方法,其中性能非常重要。在实践中,您观察到实际的方法执行时间使得内省的成本可以忽略不计。(病死率。阿姆达哈尔定律)

测试代码(承受快速&污秽)

import java.lang.reflect.Method;
import java.util.Random;
/**
 * Created by maasg on 5/10/14.
 */
public class Instrospection {
    public static void main(String [] params) throws Exception {
        Random ran = new Random();
        String[] methods = new String[] {"method1", "method2", "method3"};
        Target target = new Target();
        // Warmup
        for (int i=0; i<1000; i++) {
            String methodName = methods[ran.nextInt(3)];
            String param = new Integer(ran.nextInt()).toString();
            Method method = Target.class.getMethod(methodName, String.class);
        }
        StringBuilder builder = new StringBuilder();
        long t0 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            String methodName = methods[ran.nextInt(3)];
            String param = new Integer(ran.nextInt()).toString();
            Method method = Target.class.getMethod(methodName, String.class);
            Object result = method.invoke(target, "param");
            builder.append(result.toString());
        }
        System.out.println("Elapsed 1: "+(System.currentTimeMillis()-t0));
        Method[] invokeMethods = new Method[] {
            Target.class.getMethod(methods[0], String.class),
            Target.class.getMethod(methods[1], String.class),
            Target.class.getMethod(methods[2], String.class),
        };
        builder = new StringBuilder();
        long t1 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            String param = new Integer(ran.nextInt()).toString();
            Method method = invokeMethods[ran.nextInt(3)];
            Object result = method.invoke(target, "param");
            builder.append(result.toString());
        }
        System.out.println("Elapsed 2: "+(System.currentTimeMillis()-t1));
        builder = new StringBuilder();
        long t2 = System.currentTimeMillis();
        for (int i=0; i<100000; i++) {
            Object result = null;
            String param = new Integer(ran.nextInt()).toString();
            switch (ran.nextInt(3)) {
                case 0: result = target.method1(param);
                case 1: result = target.method2(param);
                case 2: result = target.method3(param);
            }
            builder.append(result.toString());
        }
        System.out.println("Elapsed 3: "+(System.currentTimeMillis()-t2));
    }
}

相关内容

  • 没有找到相关文章

最新更新