反射的性能差在哪里?

一直以來都在說反射慢辙培,但是根本沒有具體測試過蔑水,也沒感受過

反射真的慢嗎?

參考:http://www.reibang.com/p/4e2b49fa8ba1

是的扬蕊,很慢肤粱!

下圖是一億次循環(huán)的耗時:

  • 直接調(diào)用 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反射優(yōu)化(緩存Method) 100000000 times using 43 ms
  • reflectAsm反射優(yōu)化(沒有緩存Method) 100000000 times using 131788 ms

沒有一個可以比 直接調(diào)用 更快的。

  • 原生反射(沒有緩存Method) 大概比 直接調(diào)用 慢了 340倍
  • 原生反射(緩存Method) 大概比 直接調(diào)用 慢了 9倍

怎么優(yōu)化速度厨相?

反射的速度差異只在大量連續(xù)使用才能明顯看出來,理論上100萬次才會說反射很慢鸥鹉,對于一個單進(jìn)單出的請求來說蛮穿,反射與否根本差不了多少。

這樣就沒必要優(yōu)化了嗎毁渗,并不是践磅。

事實上各大框架注解,甚至業(yè)務(wù)系統(tǒng)中都在使用反射灸异,不能因為慢就不用了府适。
在后臺Controller中序列化請求響應(yīng)信息大量使用注解羔飞,高并發(fā)就意味著連續(xù)百萬級別調(diào)用反射成為可能,各大MVC框架都會著手解決這個問題檐春,優(yōu)化反射逻淌。

反射核心的是getMethod和invoke了,分析下兩者的耗時差距疟暖,在一億次循環(huán)下的耗時卡儒。

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

原生反射(只invoke) 100000000 times using 221 ms
原生反射(只getMethod) 100000000 times using 12849 ms
優(yōu)化思路1:緩存Method,不重復(fù)調(diào)用getMethod

證明getMethod很耗時俐巴,所以說我們要優(yōu)先優(yōu)化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最后直接調(diào)用native方法欣舵,無解了擎鸠。想復(fù)寫優(yōu)化getMethod是不可能的了,官方?jīng)]毛病缘圈。
但是我們可以不需要每次都getMethod啊劣光,我們可以緩存到redis,或者放到Spring容器中准验,就不需要每次都拿了赎线。

//通過Java Class類自帶的反射獲得Method測試,僅進(jìn)行一次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倍糊饱,離原生調(diào)用還差個9倍垂寥,所以我們繼續(xù)優(yōu)化invoke。

優(yōu)化思路2:使用reflectAsm另锋,讓invoke變成直接調(diào)用

我們看下invoke的源碼:

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

尷尬滞项,最后還是native方法,依然沒毛病夭坪。
invoke不像getMethod可以緩存起來重復(fù)用文判,沒法優(yōu)化。

所以這里需要引入ASM室梅,并做了個工具庫reflectAsm:
參考:https://blog.csdn.net/zhuoxiuwu/article/details/78619645戏仓,https://github.com/EsotericSoftware/reflectasm

“ASM 是一個 Java 字節(jié)碼操控框架。它能被用來動態(tài)生成類或者增強既有類的功能亡鼠。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件赏殃,也可以在類被加載入 Java 虛擬機(jī)之前動態(tài)改變類行為。Java class 被存儲在嚴(yán)格格式定義的 .class 文件里间涵,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱仁热、方法、屬性以及 Java 字節(jié)碼(指令)勾哩。ASM 從類文件中讀入信息后抗蠢,能夠改變類行為举哟,分析類信息,甚至能夠根據(jù)用戶要求生成新類迅矛》列桑”

使用如下:

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是沒辦法優(yōu)化的诬乞,也沒辦法做到像直接調(diào)用那么快册赛。所以大佬們腦洞大開,不用反射的invoke了震嫉。原理如下:

  • 借反射的getDeclaredMethods獲取SimpleBean.class的所有方法森瘪,然后動態(tài)生成一個繼承于MethodAccess 的子類SimpleBeanMethodAccess,動態(tài)生成一個Class文件并load到JVM中票堵。
  • SimpleBeanMethodAccess中所有方法名建立index索引扼睬,index跟方法名是映射的,根據(jù)方法名獲得index悴势,SimpleBeanMethodAccess內(nèi)部建立的switch直接分發(fā)執(zhí)行相應(yīng)的代碼窗宇,這樣methodAccess.invoke的時候,實際上是直接調(diào)用特纤。

實際上reflectAsm是有個致命漏洞的军俊,因為要生成文件,還得load進(jìn)JVM捧存,所以reflectAsm的getMethod特別慢:

  • reflectAsm反射優(yōu)化(沒有緩存Method) 100000000 times using 131788 ms

雖然getMethod很慢粪躬,但是invoke的速度是到達(dá)了直接調(diào)用的速度了。

如果能夠緩存method昔穴,那么reflectAsm的速度跟直接調(diào)用一樣镰官,而且能夠使用反射!

  • 直接調(diào)用 100000000 times using 36 ms
  • reflectAsm反射優(yōu)化(緩存Method) 100000000 times using 43 ms
  • 這其中差的7ms吗货,是reflectAsm生成一次Class文件的損耗泳唠。

下面是反射優(yōu)化的測試樣例:

//通過高性能的ReflectAsm庫進(jìn)行測試,僅進(jìn)行一次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反射優(yōu)化+緩存Method", times, watch.elapsed(TimeUnit.MILLISECONDS));
        System.out.println(result);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宙搬,一起剝皮案震驚了整個濱河市笨腥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勇垛,老刑警劉巖扇雕,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窥摄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)础淤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門崭放,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哨苛,“玉大人,你說我怎么就攤上這事币砂〗ㄇ停” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵决摧,是天一觀的道長亿蒸。 經(jīng)常有香客問我,道長掌桩,這世上最難降的妖魔是什么边锁? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮波岛,結(jié)果婚禮上茅坛,老公的妹妹穿的比我還像新娘。我一直安慰自己则拷,他們只是感情好贡蓖,可當(dāng)我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煌茬,像睡著了一般斥铺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坛善,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天晾蜘,我揣著相機(jī)與錄音,去河邊找鬼浑吟。 笑死笙纤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的组力。 我是一名探鬼主播省容,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼燎字!你這毒婦竟也來了腥椒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤候衍,失蹤者是張志新(化名)和其女友劉穎笼蛛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛉鹿,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡滨砍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惋戏。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡领追,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出响逢,到底是詐尸還是另有隱情绒窑,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布舔亭,位于F島的核電站些膨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钦铺。R本人自食惡果不足惜订雾,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望职抡。 院中可真熱鬧葬燎,春花似錦、人聲如沸缚甩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擅威。三九已至壕探,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郊丛,已是汗流浹背李请。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留厉熟,地道東北人导盅。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像揍瑟,于是被迫代替她去往敵國和親白翻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容

  • # Python 資源大全中文版 我想很多程序員應(yīng)該記得 GitHub 上有一個 Awesome - XXX 系列...
    aimaile閱讀 26,505評論 6 427
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,336評論 25 707
  • 那個夏天绢片,我遇到了你滤馍。 夏天的陽光明亮刺眼,落在胳膊上底循,曬得人生疼巢株。來草原十幾天,我已經(jīng)接了七熙涤、八份收割苜蓿的短工...
    吾三川閱讀 370評論 0 1
  • 二O一八年一月二十五日星期四阁苞,暴雪 二O一八年第一場雪從我這里跳過去了困檩,我這里只下一場凍雨,一月三日至十二日那槽,山山...
    泗四坊方閱讀 2,075評論 71 76
  • 在我剛?cè)コ啥嫉哪菚矚g逛一個有關(guān)學(xué)習(xí)的論壇倦炒,有一段時間,論壇里各種相親貼四處橫飛软瞎。我也閑著無聊在壇里發(fā)過一個相親貼...
    快跑少年閱讀 464評論 9 8