美文网首页
反射的性能差在哪里?

反射的性能差在哪里?

作者: taojian | 来源:发表于2018-07-04 18:43 被阅读0次

一直以来都在说反射慢,但是根本没有具体测试过,也没感受过

反射真的慢吗?

参考:https://www.jianshu.com/p/4e2b49fa8ba1

是的,很慢!

下图是一亿次循环的耗时:

  • 直接调用 100000000 times using 36 ms
  • 原生反射(只invoke) 100000000 times using 325 ms
  • 原生反射(只getMethod) 100000000 times using 11986 ms
  • 原生反射(缓存Method) 100000000 times using 319 ms
  • 原生反射(没有缓存Method) 100000000 times using 12169 ms
  • reflectAsm反射优化(缓存Method) 100000000 times using 43 ms
  • reflectAsm反射优化(没有缓存Method) 100000000 times using 131788 ms

没有一个可以比 直接调用 更快的。

  • 原生反射(没有缓存Method) 大概比 直接调用 慢了 340倍
  • 原生反射(缓存Method) 大概比 直接调用 慢了 9倍

怎么优化速度?

反射的速度差异只在大量连续使用才能明显看出来,理论上100万次才会说反射很慢,对于一个单进单出的请求来说,反射与否根本差不了多少。

这样就没必要优化了吗,并不是。

事实上各大框架注解,甚至业务系统中都在使用反射,不能因为慢就不用了。
在后台Controller中序列化请求响应信息大量使用注解,高并发就意味着连续百万级别调用反射成为可能,各大MVC框架都会着手解决这个问题,优化反射。

反射核心的是getMethod和invoke了,分析下两者的耗时差距,在一亿次循环下的耗时。

Method getName = SimpleBean.class.getMethod("getName");
getName.invoke(bean);

原生反射(只invoke) 100000000 times using 221 ms
原生反射(只getMethod) 100000000 times using 12849 ms
优化思路1:缓存Method,不重复调用getMethod

证明getMethod很耗时,所以说我们要优先优化getMethod,看看为什么卡?

Method getName = SimpleBean.class.getMethod("getName");
//查看源码
Method res =  privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
//再看下去
private native Field[]       getDeclaredFields0(boolean publicOnly);
private native Method[]      getDeclaredMethods0(boolean publicOnly);
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
private native Class<?>[]   getDeclaredClasses0();

getMethod最后直接调用native方法,无解了。想复写优化getMethod是不可能的了,官方没毛病。
但是我们可以不需要每次都getMethod啊,我们可以缓存到redis,或者放到Spring容器中,就不需要每次都拿了。

//通过Java Class类自带的反射获得Method测试,仅进行一次method获取
    @Test
    public void javaReflectGetOnly() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Method getName = SimpleBean.class.getMethod("getName");
        Stopwatch watch = Stopwatch.createStarted();
        for (long i = 0; i < times; i++) {
            getName.invoke(bean);
        }
        watch.stop();
        String result = String.format(formatter, "原生反射+缓存Method", times, watch.elapsed(TimeUnit.MILLISECONDS));
        System.out.println(result);
    }
  • 原生反射(缓存Method) 100000000 times using 319 ms
  • 原生反射(没有缓存Method) 100000000 times using 12169 ms

缓存Method大概快了38倍,离原生调用还差个9倍,所以我们继续优化invoke。

优化思路2:使用reflectAsm,让invoke变成直接调用

我们看下invoke的源码:

getName.invoke(bean);
//查看源码
private static native Object invoke0(Method var0, Object var1, Object[] var2);

尴尬,最后还是native方法,依然没毛病。
invoke不像getMethod可以缓存起来重复用,没法优化。

所以这里需要引入ASM,并做了个工具库reflectAsm:
参考:https://blog.csdn.net/zhuoxiuwu/article/details/78619645https://github.com/EsotericSoftware/reflectasm

“ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。”

使用如下:

MethodAccess methodAccess = MethodAccess.get(SimpleBean.class);
methodAccess.invoke(bean, "getName");

//看看MethodAccess.get(SimpleBean.class)源码,使用了反射的getMethod】
Method[] declaredMethods = type.getDeclaredMethods();

参考:https://blog.csdn.net/z69183787/article/details/51657771
invoke是没办法优化的,也没办法做到像直接调用那么快。所以大佬们脑洞大开,不用反射的invoke了。原理如下:

  • 借反射的getDeclaredMethods获取SimpleBean.class的所有方法,然后动态生成一个继承于MethodAccess 的子类SimpleBeanMethodAccess,动态生成一个Class文件并load到JVM中。
  • SimpleBeanMethodAccess中所有方法名建立index索引,index跟方法名是映射的,根据方法名获得index,SimpleBeanMethodAccess内部建立的switch直接分发执行相应的代码,这样methodAccess.invoke的时候,实际上是直接调用。

实际上reflectAsm是有个致命漏洞的,因为要生成文件,还得load进JVM,所以reflectAsm的getMethod特别慢:

  • reflectAsm反射优化(没有缓存Method) 100000000 times using 131788 ms

虽然getMethod很慢,但是invoke的速度是到达了直接调用的速度了。

如果能够缓存method,那么reflectAsm的速度跟直接调用一样,而且能够使用反射!

  • 直接调用 100000000 times using 36 ms
  • reflectAsm反射优化(缓存Method) 100000000 times using 43 ms
  • 这其中差的7ms,是reflectAsm生成一次Class文件的损耗。

下面是反射优化的测试样例:

//通过高性能的ReflectAsm库进行测试,仅进行一次methodAccess获取
    @Test
    public void reflectAsmGetOnly() {
        MethodAccess methodAccess = MethodAccess.get(SimpleBean.class);
        Stopwatch watch = Stopwatch.createStarted();
        for (long i = 0; i < times; i++) {
            methodAccess.invoke(bean, "getName");
        }
        watch.stop();
        String result = String.format(formatter, "reflectAsm反射优化+缓存Method", times, watch.elapsed(TimeUnit.MILLISECONDS));
        System.out.println(result);
    }

相关文章

  • 反射的性能差在哪里?

    一直以来都在说反射慢,但是根本没有具体测试过,也没感受过 反射真的慢吗? 参考:https://www.jians...

  • Java反射

    什么是反射? 反射的作用? 反射性能优化?

  • reflect.go包学习_之二 指针操作提高反射性能 反射应用

    reflect.go包学习_之二 指针操作提高反射性能 反射应用 反射创建实例 反射信息、反射调用方法、反射修改值...

  • 差在了哪里?

    话说,刚搞出来聊天小程序没几天的我,都没怎么嘚瑟几天,就被一个同事刺激到了,哪怕以厚脸皮自称的我,也很难在继续忍下...

  • 【广撒网】ConcurrentHashMap提供的compute

    在实际项目中,会使用反射来编写代码,为了优化反射的性能,我们需要缓存Field对象。而缓存Field对象时,我们需...

  • Java基础

    一、反射&内省 1、反射 反射会有些许性能消耗,因为它把装载期做的事情搬到了运行期。 2、JavaBean内省 内...

  • 脚诊

    足部反射区解析大全,哪里难受就按哪里,准确有效! 足部反射区解析大全,哪里难受就按哪里,准确有效! 祖国医学中的穴...

  • Java反射复习

    前言 java反射技术是java中经常使用到的技术,并且有不少的开源框架都是会使用过反射。虽然由于性能原因,目前在...

  • 那些高端、优雅的注解是怎么实现的<4> -- 使用Annotai

    概述 注解的解析可以通过反射,但反射的性能较低。所以在移动平台上,如安卓端使用,那是得不偿失的。那么Android...

  • java的反射功能

    rovider的调用都会用到java的反射功能,有人说使用反射会慢,那么到底慢在哪里呢? 反射 反射使JAVA语言...

网友评论

      本文标题:反射的性能差在哪里?

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