反射为什么慢?

1. 背景

今天刷到一篇文章,标题是反射为什么慢,一下子懵逼了,确实没想过这个问题;盲猜了一下是由于反射实际上是做了一个代理的动作,导致执行的效率是小于直接实体类去调用方法的。

2. 文章给出的解释

文章中给出的理由是因为以下4点:

  1. 反射涉及动态解析的内容,不能执行某些虚拟机优化,例如JIT优化技术
  2. 在反射时,参数需要包装成object\[\]类型,但是方法真正执行的时候,又使用拆包成真正的类型,这些动作不仅消耗时间,而且过程中会产生很多的对象,这就会导致gc,gc也会导致延时
  3. 反射的方法调用需要从数组中遍历,这个遍历的过程也比较消耗时间
  4. 不仅需要对方法的可见性进行检查,参数也需要做额外的检查

3. 结合实际理解

3.1 第一点分析

首先我们需要知道,java中的反射是一种机制,它可以在代码运行过程中,获取类的内部信息(变量、构造方法、成员方法);操作对象的属性、方法。 然后关于反射的原理,首先我们需要知道一个java项目在启动之后,会将class文件加载到堆中,生成一个class对象,这个class对象中有一个类的所有信息,通过这个class对象获取类相关信息的操作我们称为反射。

其次是JIT优化技术,首先我们需要知道在java虚拟机中有两个角色,解释器和编译器;这两者各有优劣,首先是解释器可以在项目启动的时候直接直接发挥作用,省去编译的时候,立即执行,但是在执行效率上有所欠缺;在项目启动之后,随着时间推移,编译器逐渐将机器码编译成本地代码执行,减少解释器的中间损耗,增加了执行效率。

我们可以知道JIT优化通常依赖于在编译时能够知道的静态信息,而反射的动态性可能会破坏这些假设,使得JIT编译器难以进行有效的优化。

3.2 第二点

关于第二点,我们直接写一段反射调用对象方法的demo:

java 复制代码
@Test
public void methodTest() {
    Class clazz = MyClass.class;

    try {
        //获取指定方法
        //这个注释的会报错 java.lang.NoSuchMethodException
        //Method back = clazz.getMethod("back");
        Method back = clazz.getMethod("back", String.class);
        Method say = clazz.getDeclaredMethod("say", String.class);
        //私有方法需要设置
        say.setAccessible(true);
        MyClass myClass = new MyClass("abc", 99);
        //反射调用方法
        System.out.println(back.invoke(myClass, "back"));

        say.invoke(myClass, "hello world");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在上面这段代码中,我们调用了一个invoke 方法,并且传了class对象和参数,进入到invoke方法中,我们可以看到invoke方法的入参都是Object类型的,args更是一个Object 数组,这就第二点,关于反射调用过程中的拆装箱。

java 复制代码
@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);
}

3.3 第三点

关于调用方法需要遍历这点,还是上面那个demo,我们在获取Method 对象的时候是通过调用getMethod、getDeclaredMethod方法,点击进入这个方法的源码,我们可以看到如下代码:

java 复制代码
private static Method searchMethods(Method[] methods,
                                    String name,
                                    Class<?>[] parameterTypes)
{
    Method res = null;
    String internedName = name.intern();
    for (int i = 0; i < methods.length; i++) {
        Method m = methods[i];
        if (m.getName() == internedName
            && arrayContentsEq(parameterTypes, m.getParameterTypes())
            && (res == null
                || res.getReturnType().isAssignableFrom(m.getReturnType())))
            res = m;
    }

    return (res == null ? res : getReflectionFactory().copyMethod(res));
}

我们可以看到,底层实际上也是将class对象的所有method遍历了一遍,最终才拿到我们需要的方法的,这也就是第二点,执行具体方法的时候需要遍历class对象的方法。

3.4 第四点

第4点说需要对方法和参数进行检查,也就是我们在执行具体的某一个方法的时候,我们实际上是需要校验这个方法是否可见的,如果不可见,我们还需要将这个方法设置为可见,否则如果我们直接调用这个方法的话,会报错。

同时还有一个点,在我们调用invoke方法的时候,反射类会对方法和参数进行一个校验,让我们来看一下源码:

java 复制代码
@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);
}

我们可以看到还有quickCheckMemberAccess、checkAccess 等逻辑

4. 总结

平时在反射这块用的比较少,也没针对性的去学习一下。在工作之余,还是得保持一个学习的习惯,这样子才不会出现今天这种被一个问题难倒的情况,而且才能产出更多、更优秀的方案。

相关推荐
摸爬滚打的小李42 分钟前
Spring XML + WAR + Tomcat 项目启动
后端
揣一尾游鱼yu1 小时前
Spring上(IOC)笔记
java·笔记·spring·ioc
荣码1 小时前
长上下文与大文档处理:200页PDF怎么喂给AI,我踩了4个坑
java·python
IT_陈寒2 小时前
SpringBoot自动配置的坑,这次终于爬出来了
前端·人工智能·后端
极限挑战哥2 小时前
共享电单车联营平台分账架构实践:二清合规与自动分账零人工方案
java·大数据·人工智能·微信小程序·架构·微信公众平台
RainbowFish2 小时前
# RAG 与智能体记忆:从原理到工程实践 > 一篇给学习者的总结。
后端
皮皮林5512 小时前
比 VMware 更快,更轻的虚拟机,横空出世!
java
苏三说技术2 小时前
为什么越来越多人用Arthas 4 ?
后端
Thomas.Sir2 小时前
第13课:MyBatis-Plus整合SpringBoot4.x,分页、条件查询、逻辑删除实战
java·spring boot
ServBay2 小时前
AI 开发平台出事后,API Key 泄露要怎么办
后端·aigc·ai编程