你們都說 Java 反射效率低,你知道緣由在哪裏麼

這是 ZY 第 17 篇原創技術文章html

預備知識

  1. 瞭解 Java 反射基本用法

看完本文能夠達到什麼程度

  1. 瞭解 Java 反射原理及 Java 反射效率低的緣由

文章概覽

summary

咱們在 Java 開發中,不免會接觸到反射,而在一些框架中,反射的運用更是常見。我相信,每次提到反射,你們的第一反應必定是反射效率低,儘可能少使用。
可是反射的效率到底低多少?反射效率低的緣由在哪裏?
這篇文章就來探索一下這些問題。
因爲本機上安裝的是 openjdk 12,因此這裏就使用 openjdk 12 源碼進行分析。java

咱們先看結論,而後分析一下 Java 反射的原理,過程當中你們能夠根據結論,對源碼作一些思考,而後再根據原理中的一些實現,看看 Java 反射效率低的緣由。bootstrap

零、先放結論

Java 反射效率低主要緣由是:api

  1. Method#invoke 方法會對參數作封裝和解封操做
  2. 須要檢查方法可見性
  3. 須要校驗參數
  4. 反射方法難之內聯
  5. JIT 沒法優化

1、Java 反射原理--獲取要反射的方法

1.1 反射的使用

咱們先來看看 Java 反射使用的一段代碼:數組

public class RefTest {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.zy.java.RefTest");
            Object refTest = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("refMethod");
            method.invoke(refTest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void refMethod() {
    }
}
複製代碼

咱們在調用反射時,首先會建立 Class 對象,而後獲取其 Method 對象,調用 invoke 方法。
獲取反射方法時,有兩個方法,getMethodgetDeclaredMethod,咱們就從這兩個方法開始,一步步看下反射的原理。
接下來就進入代碼分析,你們作好準備。緩存

1.2 getMethod / getDeclaredMethod

這裏咱們先總體看一下 getMethod 和 getDeclaredMethod 的實現。bash

class Class {
    @CallerSensitive
    public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 檢查方法權限
            checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
        }
        // 2. 獲取方法
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法的拷貝
        return getReflectionFactory().copyMethod(method);
    }

    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 檢查方法是權限
            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 getReflectionFactory().copyMethod(method);
    }
}
複製代碼

從上面的代碼,咱們能夠看到,獲取方法的流程分三步走:oracle

  1. 檢查方法權限
  2. 獲取方法 Method 對象
  3. 返回方法的拷貝

這裏主要有兩個區別:app

  1. getMethod 中 checkMemberAccess 傳入的是 Member.PUBLIC,而 getDeclaredMethod 傳入的是 Member.DECLARED 這兩個值有什麼區別呢?咱們看下代碼中的註釋:
interface Member {
    /** * Identifies the set of all public members of a class or interface, * including inherited members. */
    public static final int PUBLIC = 0;

    /** * Identifies the set of declared members of a class or interface. * Inherited members are not included. */
    public static final int DECLARED = 1;
}
複製代碼

註釋裏清楚的解釋了 PUBLIC 和 DECLARED 的不一樣,PUBLIC 會包括全部的 public 方法,包括父類的方法,而 DECLARED 會包括全部本身定義的方法,public,protected,private 都在此,可是不包括父類的方法。
這也正是 getMethod 和 getDeclaredMethod 的區別。
2. getMethod 中獲取方法調用的是 getMethod0,而 getDeclaredMethod 獲取方法調用的是 privateGetDeclaredMethods 關於這個區別,這裏簡單說起一下,後面具體分析代碼。
privateGetDeclaredMethods 是獲取類自身定義的方法,參數是 boolean publicOnly,表示是否只獲取公共方法。框架

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    //...
}
複製代碼

而 getMethod0 會遞歸查找父類的方法,其中會調用到 privateGetDeclaredMethods 方法。

既然咱們上面看了 getMethod 和 getDeclaredMethod 的區別,咱們天然選擇 getMethod 方法進行分析,這樣能夠走到整個流程。

1.3 getMethod 方法

getMethod 方法流程以下圖:

getMethod

class Class {
    public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 檢查方法權限
            checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
        }
        // 2. 獲取方法 Method 對象
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法拷貝
        return getReflectionFactory().copyMethod(method);
    }
}
複製代碼

咱們上面說到獲取方法分三步走:

  1. 檢查方法權限
  2. 獲取方法 Method 對象
  3. 返回方法的拷貝

咱們先看看檢查方法權限作了些什麼事情。

1.3.1 checkMemberAccess
class Class {
    private void checkMemberAccess(SecurityManager sm, int which, Class<?> caller, boolean checkProxyInterfaces) {
        /* Default policy allows access to all {@link Member#PUBLIC} members, * as well as access to classes that have the same class loader as the caller. * In all other cases, it requires RuntimePermission("accessDeclaredMembers") * permission. */
        final ClassLoader ccl = ClassLoader.getClassLoader(caller);
        if (which != Member.PUBLIC) {
            final ClassLoader cl = getClassLoader0();
            if (ccl != cl) {
                sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
            }
        }
        this.checkPackageAccess(sm, ccl, checkProxyInterfaces);
    }
}
複製代碼

在這裏能夠看到,對於非 Member.PUBLIC 的訪問,會增長一項檢測,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); 這項檢測須要運行時申請 RuntimePermission("accessDeclaredMembers")
這裏就不繼續往下看了,方法總體是在檢查是否能夠訪問對象成員。

接着看下是如何獲取方法的 Method 對象。

1.3.2 getMethod0
class Class {
    private Method getMethod0(String name, Class<?>[] parameterTypes) {
        PublicMethods.MethodList res = getMethodsRecursive(
            name,
            parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
            /* includeStatic */ true);
        return res == null ? null : res.getMostSpecific();
    }
}
複製代碼

這裏是經過 getMethodsRecursive 獲取到 MethodList 對象,而後經過 MethodList#getMostSpecific 方法篩選出對應的方法。 MethodList#getMOstSpecific 會篩選返回值類型最爲具體的方法,至於爲何會有返回值的區別,後面會講到。
(這裏的具體,指的是有兩個方法,返回值分別是 Child 和 Parent,Child 繼承自 Parent,這裏會篩選出返回值爲 Child 的方法)。

接着看 getMethodsRecursive 方法,是如何獲取方法的。

1.3.3 getMethodsRecursive
class Class {
    private PublicMethods.MethodList getMethodsRecursive(String name, Class<?>[] parameterTypes, boolean includeStatic) {
        // 1. 獲取本身的 public 方法
        Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);
        // 2. 篩選符合條件的方法,構造 MethodList 對象
        PublicMethods.MethodList res = PublicMethods.MethodList
            .filter(methods, name, parameterTypes, includeStatic);
        // 找到方法,直接返回
        if (res != null) {
            return res;
        }

        // 3. 沒有找到方法,就獲取其父類,遞歸調用 getMethodsRecursive 方法
        Class<?> sc = getSuperclass();
        if (sc != null) {
            res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
        }

        // 4. 獲取接口中對應的方法
        for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
            res = PublicMethods.MethodList.merge(
                res, intf.getMethodsRecursive(name, parameterTypes,
                                              /* includeStatic */ false));
        }

        return res;
    }
}
複製代碼

這裏獲取方法有四個步驟:

  1. 經過 privateGetDeclaredMethods 獲取本身全部的 public 方法
  2. 經過 MethodList#filter 查找 方法名,參數相同的方法,若是找到,直接返回
  3. 若是本身沒有實現對應的方法,就去父類中查找對應的方法
  4. 查找接口中對應的方法

經過上面四個步驟,最終獲取到的是一個 MethodList 對象,是一個鏈表結點,其 next 指向下一個結點。也就是說,這裏獲取到的 Method 會有多個。
這裏稍微解釋一下,在咱們平時編寫 Java 代碼時,同一個類是不能有方法名和方法參數都相同的方法的,而實際上,在 JVM 中,一個方法簽名是和 返回值,方法名,方法參數 三者相關的。 也就是說,在 JVM 中,能夠存在 方法名和方法參數都相同,可是返回值不一樣的方法。
因此這裏返回的是一個方法鏈表。
因此上面最終返回方法時會經過 MethodList#getMostSpecific 進行返回值的篩選,篩選出返回值類型最具體的方法。

這裏咱們先暫停回顧一下總體的調用鏈路:

getMethod -> getMethod0 -> getMethodsRecursive -> privateGetDeclaredMethods
複製代碼

經過函數調用,最終會調用到 privateGetDeclaredMethods 方法,也就是真正獲取方法的地方。

1.3.4 privateGetDeclaredMethods
class Class {
    private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        Method[] res;
        // 1. 經過緩存獲取 Method[]
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // 2. 沒有緩存,經過 JVM 獲取
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }
}
複製代碼

在 privateGetDeclaredMethods 獲取方法時,有兩個步驟:

  1. relectionData 經過緩存獲取
  2. 若是緩存沒有命中的話,經過 getDeclaredMethods0 獲取方法

先看看 relectionData 方法:

class Class {
    private ReflectionData<T> reflectionData() {
        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
        int classRedefinedCount = this.classRedefinedCount;
        ReflectionData<T> rd;
        if (reflectionData != null &&
            (rd = reflectionData.get()) != null &&
            rd.redefinedCount == classRedefinedCount) {
            return rd;
        }
        // else no SoftReference or cleared SoftReference or stale ReflectionData
        // -> create and replace new instance
        return newReflectionData(reflectionData, classRedefinedCount);
    }
}
複製代碼

在 Class 中會維護一個 ReflectionData 的軟引用,做爲反射數據的緩存。
ReflectionData 結構以下:

private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Cached names
        String simpleName;
        String canonicalName;
        static final String NULL_SENTINEL = new String();

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;
    }
複製代碼

能夠看到,保存了 Class 中的屬性和方法。 若是緩存爲空,就會經過 getDeclaredMethods0 從 JVM 中查找方法。
getDeclaredMethods0 是一個 native 方法,這裏暫時先不看。

經過上面幾個步驟,就獲取到 Method 數組。

這就是 getMethod 方法的整個實現了。
咱們再回過頭看一下 getDeclaredMethod 方法的實現,經過 privateGetDeclaredMethods 獲取方法之後,會經過 searchMethods 對方法進行篩選。

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
        // ...
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        // ...
    }
複製代碼

searchMethods 方法實現比較簡單,就是對比方法名,參數,方法返回值。

class Class {
    private static Method searchMethods(Method[] methods, String name, Class<?>[] parameterTypes) {
        ReflectionFactory fact = getReflectionFactory();
        Method res = null;
        for (Method m : methods) {
            // 比較方法名
            if (m.getName().equals(name)
                // 比較方法參數
                && arrayContentsEq(parameterTypes,
                                   fact.getExecutableSharedParameterTypes(m))
                // 比較返回值
                && (res == null
                    || (res.getReturnType() != m.getReturnType()
                        && res.getReturnType().isAssignableFrom(m.getReturnType()))))
                res = m;
        }
        return res;
    }
}
複製代碼
1.3.5 Method#copy

在獲取到對應方法之後,並不會直接返回,而是會經過 getReflectionFactory().copyMethod(method); 返回方法的一個拷貝。
最終調用的是 Method#copy,咱們來看看其實現。

class Method {
    Method copy() {
        // This routine enables sharing of MethodAccessor objects
        // among Method objects which refer to the same underlying
        // method in the VM. (All of this contortion is only necessary
        // because of the "accessibility" bit in AccessibleObject,
        // which implicitly requires that new java.lang.reflect
        // objects be fabricated for each reflective call on Class
        // objects.)
        if (this.root != null)
            throw new IllegalArgumentException("Can not copy a non-root Method");

        Method res = new Method(clazz, name, parameterTypes, returnType,
                                exceptionTypes, modifiers, slot, signature,
                                annotations, parameterAnnotations, annotationDefault);
        res.root = this;
        // Might as well eagerly propagate this if already present
        res.methodAccessor = methodAccessor;
        return res;
    }
}
複製代碼

會 new 一個 Method 實例並返回。
這裏有兩點要注意:

  1. 設置 root = this
  2. 會給 Method 設置 MethodAccessor,用於後面方法調用。也就是全部的 Method 的拷貝都會使用同一份 methodAccessor。

經過上面的步驟,就獲取到了須要反射的方法。
咱們再回顧一下以前的流程。

getMethod

2、Java 反射原理--調用反射方法

獲取到方法之後,經過 Method#invoke 調用方法。

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

invoke 方法的實現,分爲三步:

2.1 檢查是否有權限調用方法

這裏對 override 變量進行判斷,若是 override == true,就跳過檢查 咱們一般在 Method#invoke 以前,會調用 Method#setAccessible(true),就是設置 override 值爲 true。

2.2 獲取 MethodAccessor

在上面獲取 Method 的時候咱們講到過,Method#copy 會給 Method 的 methodAccessor 賦值。因此這裏的 methodAccessor 就是拷貝時使用的 MethodAccessor。
若是 ma 爲空,就去建立 MethodAccessor。

class Method {
    private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }
}
複製代碼

這裏會先查找 root 的 MethodAccessor,這裏的 root 在上面 Method#copy 中設置過。
若是仍是沒有找到,就去建立 MethodAccessor。

class ReflectionFactory {
    public MethodAccessor newMethodAccessor(Method method) {
        // 其中會對 noInflation 進行賦值
        checkInitted();
        // ...
        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            // 生成的是 MethodAccessorImpl
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
            NativeMethodAccessorImpl acc =
                new NativeMethodAccessorImpl(method);
            DelegatingMethodAccessorImpl res =
                new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }
}
複製代碼

這裏能夠看到,一共有三種 MethodAccessor。MethodAccessorImplNativeMethodAccessorImplDelegatingMethodAccessorImpl
採用哪一種 MethodAccessor 根據 noInflation 進行判斷,noInflation 默認值爲 false,只有指定了 sun.reflect.noInflation 屬性爲 true,纔會 採用 MethodAccessorImpl。
因此默認會調用 NativeMethodAccessorImpl。

MethodAccessorImpl 是經過動態生成字節碼來進行方法調用的,是 Java 版本的 MethodAccessor,字節碼生成比較複雜,這裏不放代碼了。你們感興趣能夠看這裏的 generate 方法。

DelegatingMethodAccessorImpl 就是單純的代理,真正的實現仍是 NativeMethodAccessorImpl。

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }

    public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException {
        return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}
複製代碼

NativeMethodAccessorImpl 是 Native 版本的 MethodAccessor 實現。

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException {
        // We can't inflate methods belonging to vm-anonymous classes because
        // that kind of class can't be referred to by name, hence can't be
        // found from the generated bytecode.
        if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            // Java 版本的 MethodAccessor
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }

        // Native 版本調用
        return invoke0(method, obj, args);
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}
複製代碼

在 NativeMethodAccessorImpl 的實現中,咱們能夠看到,有一個 numInvocations 閥值控制,numInvocations 表示調用次數。若是 numInvocations 大於 15(默認閥值是 15),那麼就使用 Java 版本的 MethodAccessorImpl。

爲何採用這個策略呢,能夠 JDK 中的註釋:

// "Inflation" mechanism. Loading bytecodes to implement
    // Method.invoke() and Constructor.newInstance() currently costs
    // 3-4x more than an invocation via native code for the first
    // invocation (though subsequent invocations have been benchmarked
    // to be over 20x faster). Unfortunately this cost increases
    // startup time for certain applications that use reflection
    // intensively (but only once per class) to bootstrap themselves.
    // To avoid this penalty we reuse the existing JVM entry points
    // for the first few invocations of Methods and Constructors and
    // then switch to the bytecode-based implementations.
    //
    // Package-private to be accessible to NativeMethodAccessorImpl
    // and NativeConstructorAccessorImpl
    private static boolean noInflation        = false;
複製代碼

Java 版本的 MethodAccessorImpl 調用效率比 Native 版本要快 20 倍以上,可是 Java 版本加載時要比 Native 多消耗 3-4 倍資源,因此默認會調用 Native 版本,若是調用次數超過 15 次之後,就會選擇運行效率更高的 Java 版本。
那爲何 Native 版本運行效率會沒有 Java 版本高呢?從 R 大博客來看,是由於 這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙做用,它就像個黑箱同樣讓虛擬機難以分析也將其內聯,因而運行時間長了以後反而是託管版本的代碼更快些

2.3 調用 MethodAccessor#invoke 實現方法的調用

在生成 MethodAccessor 之後,就調用其 invoke 方法進行最終的反射調用。
這裏咱們對 Java 版本的 MethodAccessorImpl 作個簡單的分析,Native 版本暫時不作分析。
在前面咱們提到過 MethodAccessorImpl 是經過 MethodAccessorGenerator#generate 生成動態字節碼而後動態加載到 JVM 中的。
其中生成 invoke 方法字節碼的是 MethodAccessorGenerator#emitInvoke。
咱們看其中校驗參數的一小段代碼:

// Iterate through incoming actual parameters, ensuring that each
        // is compatible with the formal parameter type, and pushing the
        // actual on the operand stack (unboxing and widening if necessary).

        // num args of other invoke bytecodes
        for (int i = 0; i < parameterTypes.length; i++) {
            // ...
            if (isPrimitive(paramType)) {
                // Unboxing code.
                // Put parameter into temporary local variable
                // astore_3 | astore_2
                // ...

                // repeat for all possible widening conversions:
                // aload_3 | aload_2
                // instanceof <primitive boxing type>
                // ifeq <next unboxing label>
                // aload_3 | aload_2
                // checkcast <primitive boxing type> // Note: this is "redundant",
                // // but necessary for the verifier
                // invokevirtual <unboxing method>
                // <widening conversion bytecode, if necessary>
                // goto <next parameter label>
                // <next unboxing label:> ...
                // last unboxing label:
                // new <IllegalArgumentException>
                // dup
                // invokespecial <IllegalArgumentException ctor>
                // athrow
            }
        }
複製代碼

經過上面的註釋以及字節碼,咱們能夠看到,生成的 invoke 方法,會對傳入的參數作校驗,其中會涉及到 unboxing 操做。

到此,基本上 Java 方法反射的原理就介紹完了。

3、Java 反射效率低的緣由

瞭解了反射的原理之後,咱們來分析一下反射效率低的緣由。

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

咱們能夠看到,invoke 方法的參數是 Object[] 類型,也就是說,若是方法參數是簡單類型的話,須要在此轉化成 Object 類型,例如 long ,在 javac compile 的時候 用了Long.valueOf() 轉型,也就大量了生成了Long 的 Object, 同時 傳入的參數是Object[]數值,那還須要額外封裝object數組。
而在上面 MethodAccessorGenerator#emitInvoke 方法裏咱們看到,生成的字節碼時,會把參數數組拆解開來,把參數恢復到沒有被 Object[] 包裝前的樣子,同時還要對參數作校驗,這裏就涉及到了解封操做。
所以,在反射調用的時候,由於封裝和解封,產生了額外的沒必要要的內存浪費,當調用次數達到必定量的時候,還會致使 GC。

2. 須要檢查方法可見性

經過上面的源碼分析,咱們會發現,反射時每次調用都必須檢查方法的可見性(在 Method.invoke 裏)

3. 須要校驗參數

反射時也必須檢查每一個實際參數與形式參數的類型匹配性(在NativeMethodAccessorImpl.invoke0 裏或者生成的 Java 版 MethodAccessor.invoke 裏);

4. 反射方法難之內聯

Method#invoke 就像是個獨木橋同樣,各處的反射調用都要擠過去,在調用點上收集到的類型信息就會很亂,影響內聯程序的判斷,使得 Method.invoke() 自身難以被內聯到調用方。參見 www.iteye.com/blog/rednax…

5. JIT 沒法優化

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.

由於反射涉及到動態加載的類型,因此沒法進行優化。

總結

上面就是對反射原理和反射效率低的一些分析。

summary

參考資料

www.iteye.com/blog/rednax…

About Me

zylab
相關文章
相關標籤/搜索