反射的性能差在哪里?

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

反射真的慢吗?

参考: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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,421评论 6 428
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,047评论 25 707
  • 那个夏天,我遇到了你。 夏天的阳光明亮刺眼,落在胳膊上,晒得人生疼。来草原十几天,我已经接了七、八份收割苜蓿的短工...
    吾三川阅读 346评论 0 1
  • 二O一八年一月二十五日星期四,暴雪 二O一八年第一场雪从我这里跳过去了,我这里只下一场冻雨,一月三日至十二日,山山...
    泗四坊方阅读 2,061评论 71 76
  • 在我刚去成都的那会喜欢逛一个有关学习的论坛,有一段时间,论坛里各种相亲贴四处横飞。我也闲着无聊在坛里发过一个相亲贴...
    快跑少年阅读 457评论 9 8