9012年了,别说java反射慢了

java 反射

java反射 调用 通过Method.invoke 委托给sun.reflect.MethodAccessor 来处理
jdk1.6中反射安全校验中存在synchronized锁 性能较差 jdk 8中移除
反射log 参考R大 写的很详细了 https://rednaxelafx.iteye.com/blog/548536

//jdk1.8
public final class Method extends Executable {
    
    /**
    * 访问器方法 优化反射
    */
    private volatile MethodAccessor methodAccessor;
    
        @CallerSensitive
        public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            MethodAccessor ma = methodAccessor;             // read volatile
            if (ma == null) {
                ma = acquireMethodAccessor();
            }
            return ma.invoke(obj, args);
        }
        
}

//jdk 1.6

public final  
    class Method extends AccessibleObject implements GenericDeclaration,   
                             Member {  
    // ...  
      
    private volatile MethodAccessor methodAccessor;  
    // For sharing of MethodAccessors. This branching structure is  
    // currently only two levels deep (i.e., one root Method and  
    // potentially many Method objects pointing to it.)  
    private Method              root;  
  
    // ...  
      
    public Object invoke(Object obj, Object... args)  
            throws IllegalAccessException, IllegalArgumentException,  
            InvocationTargetException  
    {  
        if (!override) {  
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {  
                Class caller = Reflection.getCallerClass(1);  
                Class targetClass = ((obj == null || !Modifier.isProtected(modifiers))  
                                     ? clazz  
                                     : obj.getClass());  
                boolean cached;  
                //同步锁 - - 性能开销大
                synchronized (this) {  
                    cached = (securityCheckCache == caller)  
                        && (securityCheckTargetClassCache == targetClass);  
                }  
                if (!cached) {  
                    Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);  
                    synchronized (this) {  
                    securityCheckCache = caller;  
                    securityCheckTargetClassCache = targetClass;  
                    }  
                }  
            }  
        }  
        if (methodAccessor == null) acquireMethodAccessor();  
        return methodAccessor.invoke(obj, args);  
    }  
      
    // NOTE that there is no synchronization used here. It is correct  
    // (though not efficient) to generate more than one MethodAccessor  
    // for a given Method. However, avoiding synchronization will  
    // probably make the implementation more scalable.  
    private void 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;  
            return;  
        }  
        // Otherwise fabricate one and propagate it up to the root  
        tmp = reflectionFactory.newMethodAccessor(this);  
        setMethodAccessor(tmp);  
    }  
      
    // ...  
}  

MethodAccessor

MethodAccessor接口 定义了反射调用方法

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

NativeMethodAccessorImpl类

NativeMethodAccessorImpl类 默认的 MethodAccessor 实现类,MethodAccessorImpl是抽象类,(为什么不用Abstract开头。。。)

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }
    
    //超过15次 使用java生成代理类
     private static int inflationThreshold = 15;
    
    //参考 R大 https://rednaxelafx.iteye.com/blog/548536
    @CallerSensitive
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        // Sun的JDK使用了“inflation”的技巧 开头若干次使用 native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,
        // 生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。 
        //统计反射调用次数 超过膨胀阈值 使用 MethodAccessorGenerator 使用 asm 生成代理类 用于反射优化
        // Java实现的版本在初始化时需要较多时间,但长久来说性能较好
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
        //每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }
        // 调用native 方法 ,运行时 解释 字节码为 机器码 ,解释一行运行一行 , 时间短 执行时间 快
        // native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。
        
        return invoke0(this.method, var1, var2);
    }

}

@CallSensitive

@CallSensitive是JVM中专用的注解,在类加载过过程中是可以常常看到这个注解的身影的,
@CallSensitive用来找到真正发起反射请求的类

    @CallerSensitive
    public static Class<?> forName(String className)
    throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

注意:Reflection.getCallerClass()方法调用所在的方法必须用@CallerSensitive进行注解

这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,原理是当时反射只检查固定深度的调用者的类,
看它有没有特权,例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够权限群访问某些信息
,那我就可以通过双重反射去达到目的:反射相关的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,
反射2检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。使用CallerSensitive后,
getCallerClass不再用固定深度去寻找actual caller(“我”),
而是把所有跟反射相关的接口方法都标注上CallerSensitive,搜索时凡看到该注解都直接跳过,
这样就有效解决了前面举例的问题

MethodAccessorGenerator

生成反射 代理类

class MethodAccessorGenerator extends AccessorGenerator {
    
   protected ClassFileAssembler asm;
    
   public MethodAccessor generateMethod(Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6) {
        return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null);
   }
   
   //使用 类文件汇编 ClassFileAssembler asm 生成代理类
   private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
           ByteVector var10 = ByteVectorFactory.create();
           this.asm = new ClassFileAssembler(var10);
           this.declaringClass = var1;
           this.parameterTypes = var3;
           this.returnType = var4;
           this.modifiers = var6;
           this.isConstructor = var7;
           this.forSerialization = var8;
           this.asm.emitMagicAndVersion();
           short var11 = 42;
           boolean var12 = this.usesPrimitiveTypes();
           if (var12) {
               var11 = (short)(var11 + 72);
           }
   
           if (var8) {
               var11 = (short)(var11 + 2);
           }
   
           var11 += (short)(2 * this.numNonPrimitiveParameterTypes());
           this.asm.emitShort(add(var11, (short)1));
           final String var13 = generateName(var7, var8);
           this.asm.emitConstantPoolUTF8(var13);
           this.asm.emitConstantPoolClass(this.asm.cpi());
           this.thisClass = this.asm.cpi();
           if (var7) {
               if (var8) {
                   this.asm.emitConstantPoolUTF8("sun/reflect/SerializationConstructorAccessorImpl");
               } else {
                   this.asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl");
               }
           } else {
               this.asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl");
           }
   
           this.asm.emitConstantPoolClass(this.asm.cpi());
           this.superClass = this.asm.cpi();
           this.asm.emitConstantPoolUTF8(getClassName(var1, false));
           this.asm.emitConstantPoolClass(this.asm.cpi());
           this.targetClass = this.asm.cpi();
           short var14 = 0;
           if (var8) {
               this.asm.emitConstantPoolUTF8(getClassName(var9, false));
               this.asm.emitConstantPoolClass(this.asm.cpi());
               var14 = this.asm.cpi();
           }
   
           this.asm.emitConstantPoolUTF8(var2);
           this.asm.emitConstantPoolUTF8(this.buildInternalSignature());
           this.asm.emitConstantPoolNameAndType(sub(this.asm.cpi(), (short)1), this.asm.cpi());
           if (this.isInterface()) {
               this.asm.emitConstantPoolInterfaceMethodref(this.targetClass, this.asm.cpi());
           } else if (var8) {
               this.asm.emitConstantPoolMethodref(var14, this.asm.cpi());
           } else {
               this.asm.emitConstantPoolMethodref(this.targetClass, this.asm.cpi());
           }
   
           this.targetMethodRef = this.asm.cpi();
           if (var7) {
               this.asm.emitConstantPoolUTF8("newInstance");
           } else {
               this.asm.emitConstantPoolUTF8("invoke");
           }
   
           this.invokeIdx = this.asm.cpi();
           if (var7) {
               this.asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;");
           } else {
               this.asm.emitConstantPoolUTF8("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
           }
   
           this.invokeDescriptorIdx = this.asm.cpi();
           this.nonPrimitiveParametersBaseIdx = add(this.asm.cpi(), (short)2);
   
           for(int var15 = 0; var15 < var3.length; ++var15) {
               Class var16 = var3[var15];
               if (!isPrimitive(var16)) {
                   this.asm.emitConstantPoolUTF8(getClassName(var16, false));
                   this.asm.emitConstantPoolClass(this.asm.cpi());
               }
           }
   
           this.emitCommonConstantPoolEntries();
           if (var12) {
               this.emitBoxingContantPoolEntries();
           }
   
           if (this.asm.cpi() != var11) {
               throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
           } else {
               this.asm.emitShort((short)1);
               this.asm.emitShort(this.thisClass);
               this.asm.emitShort(this.superClass);
               this.asm.emitShort((short)0);
               this.asm.emitShort((short)0);
               this.asm.emitShort((short)2);
               this.emitConstructor();
               this.emitInvoke();
               this.asm.emitShort((short)0);
               var10.trim();
               final byte[] var17 = var10.getData();
               return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                   public MagicAccessorImpl run() {
                       try {
                           return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                       } catch (IllegalAccessException | InstantiationException var2) {
                           throw new InternalError(var2);
                       }
                   }
               });
           }
       }
    
}

我们想看下
MethodAccessorGenerator 生成的代理类,怎么做呢。

使用 sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB 命令
jps 查看pid
点击File->Attach to...输入pid
点击 Tools -> Class Browser
🔍 GeneratedMethodAccessor
点击 save class
保存如下 : /sun/reflect/GeneratedMethodAccessor1.class

这种方式 生成的不是代理对象而是字节码的自己数组,所以只能用来保存成文件,用于反编译。

用反编译工具 JD-GUI查看 字节码为:

package sun.reflect;

public class GeneratedMethodAccessor1
  extends MethodAccessorImpl
{
  /* Error */
  public Object invoke(Object paramObject, Object[] paramArrayOfObject)
    throws java.lang.reflect.InvocationTargetException
  {
    // Byte code:
    //   0: aload_1
    //   1: ifnonnull +11 -> 12
    //   4: new 18  java/lang/NullPointerException
    //   7: dup
    //   8: invokespecial 26    java/lang/NullPointerException:<init>   ()V
    //   11: athrow
    //   12: aload_1
    //   13: checkcast 6    proxy/Subject
    //   16: aload_2
    //   17: ifnull +19 -> 36
    //   20: aload_2
    //   21: arraylength
    //   22: sipush 0
    //   25: if_icmpeq +11 -> 36
    //   28: new 20 java/lang/IllegalArgumentException
    //   31: dup
    //   32: invokespecial 27   java/lang/IllegalArgumentException:<init>   ()V
    //   35: athrow
    //   36: invokeinterface 10 1 0
    //   41: aconst_null
    //   42: areturn
    //   43: invokespecial 40   java/lang/Object:toString   ()Ljava/lang/String;
    //   46: new 20 java/lang/IllegalArgumentException
    //   49: dup_x1
    //   50: swap
    //   51: invokespecial 30   java/lang/IllegalArgumentException:<init>   (Ljava/lang/String;)V
    //   54: athrow
    //   55: new 22 java/lang/reflect/InvocationTargetException
    //   58: dup_x1
    //   59: swap
    //   60: invokespecial 33   java/lang/reflect/InvocationTargetException:<init>  (Ljava/lang/Throwable;)V
    //   63: athrow
    // Local variable table:
    //   start  length  slot    name    signature
    //   0  64  0   this    GeneratedMethodAccessor1
    //   0  64  1   paramObject Object
    //   0  64  2   paramArrayOfObject  Object[]
    //   43 1   3   localClassCastException ClassCastException
    //   55 1   4   localThrowable  Throwable
    // Exception table:
    //   from   to  target  type
    //   12 36  43  java/lang/ClassCastException
    //   12 36  43  java/lang/NullPointerException
    //   36 41  55  java/lang/Throwable
  }
}

源码为:


package sun.reflect;

import java.lang.reflect.InvocationTargetException;
import proxy.Subject;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
    public Object invoke(Object var1, Object[] var2) throws InvocationTargetException {
        if (var1 == null) {
            throw new NullPointerException();
        } else {
            Subject var10000;
            try {
                var10000 = (Subject)var1;
                if (var2 != null && var2.length != 0) {
                    throw new IllegalArgumentException();
                }
            } catch (NullPointerException | ClassCastException var4) {
                throw new IllegalArgumentException(var4.toString());
            }

            try {
                var10000.speak();
                return null;
            } catch (Throwable var3) {
                throw new InvocationTargetException(var3);
            }
        }
    }

    public GeneratedMethodAccessor1() {
    }
}

可以看到jdk生成的 反射调用 代理类 很简单,和直接调用相差无几。
多了一些校验 如 类型转换校验 非空校验等
13: checkcast 6 proxy/Subject

so 以后有人说java反射慢,你可以问他。请问您说的是那个版本?

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

推荐阅读更多精彩内容