如何提升使用Java反射的效率?

前言

在咱們平時的工做或者面試中,都會常常遇到「反射」這個知識點,經過「反射」咱們能夠動態的獲取到對象的信息以及靈活的調用對象方法等,可是在使用的同時又伴隨着另外一種聲音的出現,那就是「反射」很慢,要少用。難道反射真的很慢?那跟咱們平時正常建立對象調用方法比慢多少? 估計不少人都沒去測試過,只是」道聽途說「。下面咱們就直接經過一些測試用例來直觀的感覺一下」反射「。java

正文

準備測試對象

下面先定義一個測試的類TestUser,只有idname屬性,以及它們的getter/setter方法,另外還有一個自定義的sayHi方法。git

public class TestUser {
    private Integer id;
    private String name;
    
    public String sayHi(){
        return "hi";
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
複製代碼
測試建立100萬個對象
// 經過普通方式建立TestUser對象
@Test
public void testCommon(){
    long start = System.currentTimeMillis();
    TestUser user = null;
    int i = 0;
    while(i<1000000){
        ++i;
        user = new TestUser();
    }
    long end = System.currentTimeMillis();
    System.out.println("普通對象建立耗時:"+(end - start ) + "ms");
}

//普通對象建立耗時:10ms
複製代碼
// 經過反射方式建立TestUser對象
@Test
public void testReflexNoCache() throws Exception {
    long start = System.currentTimeMillis();
    TestUser user = null;
    int i = 0;
    while(i<1000000){
        ++i;
        user = (TestUser) Class.forName("ReflexDemo.TestUser").newInstance();
    }
    long end = System.currentTimeMillis();
    System.out.println("無緩存反射建立對象耗時:"+(end - start ) + "ms");
}

//無緩存反射建立對象耗時:926ms
複製代碼

在上面這兩個測試方法中,筆者各自測了5次,把他們消耗的時間取了一個平均值,在輸出結果中能夠看到一個是10ms,一個是926ms,在建立100W個對象的狀況下,反射竟然慢了90倍左右。wtf?差距竟然這麼大?難道反射真的這麼慢?下面筆者換一種反射的姿式,繼續測試一下,看看結果如何?github

// 經過緩存反射方式建立TestUser對象
@Test
public void testReflexWithCache() throws Exception {
    long start = System.currentTimeMillis();
    TestUser user = null;
    Class rUserClass = Class.forName("RefleDemo.TestUser");
    int i = 0;
    while(i<1000000){
        ++i;
        user = (TestUser) rUserClass.newInstance();
    }
    long end = System.currentTimeMillis();
    System.out.println("經過緩存反射建立對象耗時:"+(end - start ) + "ms");
}

//經過緩存反射建立對象耗時:41ms
複製代碼

咦?這種操做只須要41ms了,大大提升了反射建立對象的效率。爲何會快這麼多呢?面試

其實經過代碼咱們能夠發現,是Class.forName這個方法比較耗時,它實際上調用了一個本地方法,經過這個方法來要求JVM查找並加載指定的類。因此咱們在項目中使用的時候,能夠把Class.forName返回的Class對象緩存起來,下一次使用的時候直接從緩存裏面獲取,這樣就極大的提升了獲取Class的效率。同理,在咱們獲取Constructor、Method等對象的時候也能夠緩存起來使用,避免每次使用時再來耗費時間建立。緩存

測試反射調用方法
@Test
public void testReflexMethod() throws Exception {
    long start = System.currentTimeMillis();
    Class testUserClass = Class.forName("RefleDemo.TestUser");
    TestUser testUser = (TestUser) testUserClass.newInstance();
    Method method = testUserClass.getMethod("sayHi");
    int i = 0;
    while(i<100000000){
        ++i;
        method.invoke(testUser);
    }
    long end = System.currentTimeMillis();
    System.out.println("反射調用方法耗時:"+(end - start ) + "ms");
}

//反射調用方法耗時:330ms
複製代碼
@Test
public void testReflexMethod() throws Exception {
    long start = System.currentTimeMillis();
    Class testUserClass = Class.forName("RefleDemo.TestUser");
    TestUser testUser = (TestUser) testUserClass.newInstance();
    Method method = testUserClass.getMethod("sayHi");
    int i = 0;
    while(i<100000000){
        ++i;
        method.setAccessible(true);
        method.invoke(testUser);
    }
    long end = System.currentTimeMillis();
    System.out.println("setAccessible=true 反射調用方法耗時:"+(end - start ) + "ms");
}

//setAccessible=true 反射調用方法耗時:188ms
複製代碼

這裏咱們反射調用sayHi方法1億次,在調用了method.setAccessible(true)後,發現快了將近一半。查看API能夠了解到,jdk在設置獲取字段,調用方法的時候會執行安全訪問檢查,而此類操做會比較耗時,因此經過setAccessible(true)的方式能夠關閉安全檢查,從而提高反射效率。安全

極致的反射

除了上面的手段,還有沒有什麼辦法能夠更極致的使用反射呢?這裏介紹一個高性能反射工具包ReflectASM。它是經過字節碼生成的方式來實現的反射機制,下面是一個跟java反射的性能比較。markdown

這裏就不介紹它的用法了,有興趣的朋友能夠直接傳送過去:github.com/EsotericSof…異步

結語

最後總結一下,爲了更好的使用反射,咱們應該在項目啓動的時候將反射所須要的相關配置及數據加載進內存中,在運行階段都從緩存中取這些元數據進行反射操做。你們也不用害怕反射,虛擬機在不斷的優化,只要咱們方法用的對,它並無」傳聞「中的那麼慢,當咱們對性能有極致追求的時候,能夠考慮經過三方包,直接對字節碼進行操做。工具


公衆號博文同步Github倉庫,有興趣的朋友能夠幫忙給個Star哦,碼字不易,感謝支持。oop

github.com/PeppaLittle…

推薦閱讀

Java日誌正確使用姿式
使用ConcurrentHashMap必定線程安全?
大白話搞懂什麼是同步/異步/阻塞/非阻塞
論JVM爆炸的幾種姿式及自救方法

有收穫的話,就點個贊吧

關注「深夜裏的程序猿」,分享最乾的乾貨

相關文章
相關標籤/搜索