反射为什么慢?

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. 总结

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

相关推荐
回家路上绕了弯4 分钟前
分布式锁原理深度解析:从理论到实践
分布式·后端
rockmelodies12 分钟前
亿赛通脚本远程调试配置技巧
java·亿赛通·debug调试
磊磊磊磊磊20 分钟前
用AI做了个排版工具,分享一下如何高效省钱地用AI!
前端·后端·react.js
❥ღ Komo·20 分钟前
K8s蓝绿发布实战:零停机部署秘籍
java·开发语言
小安同学iter25 分钟前
天机学堂-排行榜功能-day08(六)
java·redis·微服务·zset·排行榜·unlink·天机学堂
hgz071028 分钟前
Spring Boot Starter机制
java·spring boot·后端
daxiang1209220529 分钟前
Spring boot服务启动报错 java.lang.StackOverflowError 原因分析
java·spring boot·后端
我家领养了个白胖胖29 分钟前
极简集成大模型!Spring AI Alibaba ChatClient 快速上手指南
java·后端·ai编程
jiayong2330 分钟前
Markdown编辑完全指南
java·编辑器
heartbeat..1 小时前
深入理解 Redisson:分布式锁原理、特性与生产级应用(Java 版)
java·分布式·线程·redisson·