溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是Java反射及性能

發(fā)布時間:2021-10-11 21:26:18 來源:億速云 閱讀:128 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“什么是Java反射及性能”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“什么是Java反射及性能”吧!

目錄
  • 一、準備

  • 二、反射調(diào)用流程

    • 1.反射的使用

    • 2.getMethod 和 getDeclaredMethod區(qū)別

  • 三、調(diào)用反射方法

    • 四、反射效率低的原因

      • 五、反射優(yōu)化

      一、準備

      注:本案例針對JDK1.8

      測試代碼:

      【TestRef.java】
      public class TestRef {
       
          public static void main(String[] args) {
              try {
                  Class<?> clazz = Class.forName("com.allen.commons.entity.CommonTestEntity");
                  Object refTest = clazz.newInstance();
                  Method method = clazz.getMethod("defaultMethod");
                  //Method method1 = clazz.getDeclaredMethod("defaultMethod");
                  method.invoke(refTest);
              } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                  e.printStackTrace();
              }
       
          }
      }
      ---------------------------------------------------------------------------------------
      【CommonTestEntity.java】
      public class CommonTestEntity {
       
          static {
              System.out.println("CommonTestEntity執(zhí)行類加載...");
          }
       
          public CommonTestEntity() {
              System.out.println(this.getClass() + " | CommonTestEntity實例初始化 | " + this.getClass().getClassLoader());
          }
       
          public void defaultMethod() {
              System.out.println("執(zhí)行實例方法:defaultMethod");
          }
      }

      二、反射調(diào)用流程

      1.反射的使用

      • 1)創(chuàng)建class對象(類加載,使用當前方法所在類的ClassLoader來加載)

      • 2)獲取Method對象(getMethod getDeclaredMethod

      • 3)調(diào)用invoke方法

      2.getMethod 和 getDeclaredMethod區(qū)別

      getMethod源碼如下:

      public Method getMethod(String name, Class<?>... parameterTypes)
              throws NoSuchMethodException, SecurityException {
              Objects.requireNonNull(name);
              SecurityManager sm = System.getSecurityManager();
              if (sm != null) {
                  // 1. 檢查方法權(quán)限
                  checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
              }
              // 2. 獲取方法
              Method method = getMethod0(name, parameterTypes);
              if (method == null) {
                  throw new NoSuchMethodException(methodToString(name, parameterTypes));
              }
              // 3. 返回方法
              return method;
          }
      ---------------------------------------------------------------------------------------
      public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
              throws NoSuchMethodException, SecurityException {
              Objects.requireNonNull(name);
              SecurityManager sm = System.getSecurityManager();
              if (sm != null) {
                  // 1. 檢查方法是權(quán)限
                  checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
              }
              // 2. 獲取方法
              Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
              if (method == null) {
                  throw new NoSuchMethodException(methodToString(name, parameterTypes));
              }
              // 3. 返回方法
              return method;
      }

      獲取方法的流程分三步走:

      • a.檢查方法權(quán)限

      • b.獲取方法 Method 對象

      • c.返回方法

      主要有兩個區(qū)別:

      1.getMethod checkMemberAccess 傳入的是 Member.PUBLIC,而 getDeclaredMethod 傳入的是 Member.DECLARED 。

      代碼中的注釋:

      什么是Java反射及性能

      注釋里解釋了 PUBLIC DECLARED 的不同,PUBLIC 會包括所有的 public 方法,包括父類的方法,而 DECLARED 會包括所有自己定義的方法,public,protected,private 都在此,但是不包括父類的方法。

      2.getMethod 中獲取方法調(diào)用的是 getMethod0,而 getDeclaredMethod 獲取方法調(diào)用的是 privateGetDeclaredMethods privateGetDeclaredMethods 是獲取類自身定義的方法,參數(shù)是 boolean publicOnly,表示是否只獲取公共方法。

      privateGetDeclaredMethods 源碼如下:

      // Returns an array of "root" methods. These Method objects must NOT
          // be propagated to the outside world, but must instead be copied
          // via ReflectionFactory.copyMethod.
          private Method[] privateGetDeclaredMethods(boolean publicOnly) {
              checkInitted();
              Method[] res;
              ReflectionData<T> rd = reflectionData();
              if (rd != null) {
                  res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
                  if (res != null) return res;
              }
              // No cached value available; request value from VM
              res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
              if (rd != null) {
                  if (publicOnly) {
                      rd.declaredPublicMethods = res;
                  } else {
                      rd.declaredMethods = res;
                  }
              }
              return res;
          }

      relectionData 通過緩存獲取

      ②如果緩存沒有命中的話,通過 getDeclaredMethods0 獲取方法

      getMethod0源碼如下:

      private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
              MethodArray interfaceCandidates = new MethodArray(2);
              Method res =  privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
              if (res != null)
                  return res;
       
              // Not found on class or superclass directly
              interfaceCandidates.removeLessSpecifics();
              return interfaceCandidates.getFirst(); // may be null
          }

      其中privateGetMethodRecursive方法中也會調(diào)用到privateGetDeclaredMethods方法和searchMethods方法

      3.getMethod 方法流程

      什么是Java反射及性能

      4.getDeclaredMethod方法流程

      什么是Java反射及性能

      三、調(diào)用反射方法

      invoke源碼:

      class Method {
          public Object invoke(Object obj, Object... args)
              throws IllegalAccessException, IllegalArgumentException,
                 InvocationTargetException
          {
              if (!override) {
                  Class<?> caller = Reflection.getCallerClass();
                  // 1. 檢查權(quán)限
                  checkAccess(caller, clazz,
                              Modifier.isStatic(modifiers) ? null : obj.getClass(),
                              modifiers);
              }
              // 2. 獲取 MethodAccessor
              MethodAccessor ma = methodAccessor;             // read volatile
              if (ma == null) {
                  // 創(chuàng)建 MethodAccessor
                  ma = acquireMethodAccessor();
              }
              // 3. 調(diào)用 MethodAccessor.invoke
              return ma.invoke(obj, args);
          }
      }

      Method.invoke()實際上并不是自己實現(xiàn)的反射調(diào)用邏輯,而是委托給sun.reflect.MethodAccessor來處理。

      每個實際的Java方法只有一個對應(yīng)的Method對象作為root(實質(zhì)上就是Method類的一個成員變量)。每次在通過反射獲取Method對象時新創(chuàng)建Method對象把root封裝起來。在第一次調(diào)用一個實際Java方法對應(yīng)得Method對象的invoke()方法之前,實現(xiàn)調(diào)用邏輯的MethodAccessor對象是第一次調(diào)用時才會新建并更新給root,然后調(diào)用MethodAccessor.invoke()真正完成反射調(diào)用。

      MethodAccessor只是單方法接口,其invoke()方法與Method.invoke()的對應(yīng)。創(chuàng)建MethodAccessor實例的是ReflectionFactory

      MethodAccessor實現(xiàn)有兩個版本,一個是Java實現(xiàn)的,另一個是native code實現(xiàn)的。

      Java 版本的 MethodAccessorImpl 調(diào)用效率比 Native 版本要快 20 倍以上,但是 Java 版本加載時要比 Native 多消耗 3-4 倍資源,所以默認會調(diào)用 Native 版本,如果調(diào)用次數(shù)超過 15 次以后,就會選擇運行效率更高的 Java 版本。

      Native版本中的閾值(靜態(tài)常量)

      什么是Java反射及性能

      四、反射效率低的原因

      1.Method#invoke 方法會對參數(shù)做封裝和解封操作

      我們可以看到,invoke 方法的參數(shù)是 Object[] 類型,也就是說,如果方法參數(shù)是簡單類型(8中基本數(shù)據(jù)類型)的話,需要在此轉(zhuǎn)化成 Object 類型,例如 long ,在 javac compile 的時候 用了Long.valueOf() 轉(zhuǎn)型,也就大量了生成了Long 的 Object, 同時 傳入的參數(shù)是Object[]數(shù)值,那還需要額外封裝object數(shù)組。

      而在上面 MethodAccessorGenerator#emitInvoke 方法里我們看到,生成的字節(jié)碼時,會把參數(shù)數(shù)組拆解開來,把參數(shù)恢復(fù)到?jīng)]有被 Object[] 包裝前的樣子,同時還要對參數(shù)做校驗,這里就涉及到了解封操作。

      因此,在反射調(diào)用的時候,因為封裝和解封,產(chǎn)生了額外的不必要的內(nèi)存浪費,當調(diào)用次數(shù)達到一定量的時候,還會導(dǎo)致 GC。

      2.需要檢查方法可見性

      checkAccess方法

      3.需要遍歷方法并校驗參數(shù)

      PrivateGetMethodRecursive中的searhMethod

      4.JIT 無法優(yōu)化

      在 JavaDoc 中提到:

      Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.

      五、反射優(yōu)化

      1.(網(wǎng)上看到)盡量不要getMethods()后再遍歷篩選,而直接用getMethod(methodName)來根據(jù)方法名獲取方法

      但是在源碼中獲取方法的時候,在searchMethods方法中,其實也是采用遍歷所有方法的方式。但是相比getMethod,getDeclaredMethod遍歷的方法數(shù)量相對較少,因為不包含父類的方法。

      2.緩存class對象

      a)Class.forName性能比較差

      b)如上所述,在獲取具體方法時,每次都要調(diào)用native方法獲取方法列表并遍歷列表,判斷入?yún)㈩愋秃头祷仡愋汀⒎瓷涞玫降?code>method/field/constructor對象做緩存,將極大的提高性能。

      3.涉及動態(tài)代理的:在實際使用中,CGLIB和Javassist基于動態(tài)代碼的代理實現(xiàn),性能要優(yōu)于JDK自帶的動態(tài)代理

      JDK自帶的動態(tài)代理是基于接口的動態(tài)代理,相比較直接的反射操作,性能還是高很多,因為接口實例相關(guān)元數(shù)據(jù)在靜態(tài)代碼塊中創(chuàng)建并且已經(jīng)緩存在類成員屬性中,在運行期間是直接調(diào)用,沒有額外的反射開銷。

      4.使用ReflectASM,通過生成字節(jié)碼的方式加快反射(使用難度大)

      到此,相信大家對“什么是Java反射及性能”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!

      向AI問一下細節(jié)

      免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

      AI