Java反射性能探讨

news/2024/5/19 4:41:14 标签: java, 源码, 反射

文章目录

  • 反射是否影响性能
    • 为什么反射影响性能
      • method.invoke & field.set 性能
      • Class.getMethod & Class.getDeclareField 性能
      • 测试结论
  • 反射如何影响性能
  • 如何避免反射导致的性能问题
    • 其他问题

反射是否影响性能

为了放大问题找到共性,采用逐渐扩大测试次数(executeCount)、每次测试多次取平均值(averageCount)的方式。针对同一个属性分别就直接调用方法操作属性、反射调用方法操作属性、直接操作属性、反射操作属性,分别从1-1000000,每个数量级测试一次:

java">public class Person {

    public int age;

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
java">import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionPerformanceActivity {

    public static void main(String[] args) throws Exception {
        //总共执行次数: 单次耗时时间短, 多执行几次, 放大耗时差距
        int executeCount = 1;

        //平均次数: 多进行几轮实验取平均值, 排除偶发干扰
        int averageCount = 10;

        long reflectMethodCostTime=0;
        long normalMethodCostTime=0;
        long reflectFieldCostTime=0;
        long normalFieldCostTime=0;
        for (int i = 0; i < averageCount; i++) {
            reflectMethodCostTime += getReflectCallMethodCostTime(executeCount);
            normalMethodCostTime += getNormalCallMethodCostTime(executeCount);
            reflectFieldCostTime += getReflectCallFieldCostTime(executeCount);
            normalFieldCostTime += getNormalCallFieldCostTime(executeCount);
        }

        System.out.println("执行直接调用方法总耗时:" + normalMethodCostTime/averageCount + " 毫秒");
        System.out.println("执行反射调用方法总耗时:" + reflectMethodCostTime/averageCount + " 毫秒");
        System.out.println("执行普通调用实例总耗时:" + normalFieldCostTime/averageCount + " 毫秒");
        System.out.println("执行反射调用实例总耗时:" + reflectFieldCostTime/averageCount + " 毫秒");
    }

    /**
     * 反射操作方法总耗时
     * @param count
     * @return
     */
    private static long getReflectCallMethodCostTime(int count) throws Exception {
        long startTime = System.currentTimeMillis();

        Person user = new Person(12);
        for(int index = 0 ; index < count; index++){
            Method setAge = Person.class.getMethod("setAge", int.class);
            setAge.setAccessible(true);
            setAge.invoke(user, index);
        }

        return System.currentTimeMillis() - startTime;
    }

    /**
     * 反射操作属性总耗时
     * @param count
     * @return
     */
    private static long getReflectCallFieldCostTime(int count) throws Exception {
        long startTime = System.currentTimeMillis();

        Person user = new Person(12);
        for(int index = 0 ; index < count; index++){
            Field ageField = Person.class.getDeclaredField("age");
            ageField.set(user, index);
        }

        return System.currentTimeMillis()-startTime;
    }

    /**
     * 实例直接操作方法总耗时
     */
    private static long getNormalCallMethodCostTime(int count){
        long startTime = System.currentTimeMillis();

        Person user = new Person(12);
        for(int index = 0 ; index < count; index++){
            user.setAge(index);
        }

        return System.currentTimeMillis() - startTime;
    }

    /**
     * 实例直接操作属性总耗时
     */
    private static long getNormalCallFieldCostTime(int count){
        long startTime = System.currentTimeMillis();

        Person user = new Person(12);
        for(int index = 0 ; index < count; index++){
            user.age = index;
        }

        return System.currentTimeMillis() - startTime;
    }

}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9e2J5Mc-1637682940030)(https://note.youdao.com/yws/public/resource/4a01e14b87652ad2c38721ffff0e5c3a/xmlnote/3170F7D325D3432BBE9F78D4D5270DE3/23906)]

测试结论:

  1. 反射的确会导致性能问题;

  2. 反射导致的性能问题是否严重跟使用的次数有关系。

  3. 如果控制在1000次以内,基本上没什么差别;如果调用次数超过了1000次,性能差异会很明显;调用次数越多性能差异越大;

  4. 四种访问方式中:

    直接操作属性的方式效率最高;

    其次是直接调用方法操作属性的方式,耗时约为直接操作属性的1.2倍;

    接着是通过反射调用方法操作属性的方式,耗时约为直接操作属性的17172倍;

    最慢的是通过反射调用方法操作属性的方式,耗时约为直接操作属性的25993倍。

为什么反射影响性能

分析反射相关代码,由于进行上述实验时,没有启用Java安全机制,因此,对于反射方法setAge.setAccessible(true)中处理逻辑也只是设置属性值而已,不会产生明显的性能差异。

那么,具体的性能差异应该就在这几个方法中:Class.getMethodmethod.invokeClass.getDeclaredFieldfield.set

method.invoke & field.set 性能

对上述getReflectCallMethodCostTimegetReflectCallFieldCostTime做如下改造:

/**
 * 反射操作方法总耗时
 * @param count
 * @return
 */
private static long getReflectCallMethodCostTime(int count) throws Exception {
    long startTime = System.currentTimeMillis();

    Person user = new Person(12);
    Method setAge = Person.class.getMethod("setAge", int.class);
    setAge.setAccessible(true);
    for(int index = 0 ; index < count; index++){
        setAge.invoke(user, index);
    }

    return System.currentTimeMillis() - startTime;
}

/**
 * 反射操作属性总耗时
 * @param count
 * @return
 */
private static long getReflectCallFieldCostTime(int count) throws Exception {
    long startTime = System.currentTimeMillis();

    Person user = new Person(12);
    Field ageField = Person.class.getDeclaredField("age");
    for(int index = 0 ; index < count; index++){
        ageField.set(user, index);
    }

    return System.currentTimeMillis()-startTime;
}

测试结果:

截图

Class.getMethod & Class.getDeclareField 性能

再次对上述getReflectCallMethodCostTimegetReflectCallFieldCostTime做如下改造:

java">/**
 * 反射操作方法总耗时
 * @param count
 * @return
 */
private static long getReflectCallMethodCostTime(int count) throws Exception {
    long startTime = System.currentTimeMillis();

    Method setAge = null;
    for(int index = 0 ; index < count; index++){
        setAge = Person.class.getMethod("setAge", int.class);
    }
    setAge.setAccessible(true);

    return System.currentTimeMillis() - startTime;
}

/**
 * 反射操作属性总耗时
 * @param count
 * @return
 */
private static long getReflectCallFieldCostTime(int count) throws Exception {
    long startTime = System.currentTimeMillis();

    Field ageField = null;
    for(int index = 0 ; index < count; index++){
        ageField = Person.class.getDeclaredField("age");
    }

    return System.currentTimeMillis()-startTime;
}

测试结果:

截图

测试结论

  1. method.invokefield.set相对于普通直接操作属性速度要慢了许多,其中,后者的耗时是前者的1.4倍;
  2. Class.getMethodClass.getDeclaredField相对于 1 性能更差,前者耗时是或者的1.6倍;
  3. 反射方法Class.getMethodClass.getDeclaredFieldmethod.invokefield.set耗时多的多,从实验结果来看,至少是2个数量级的差距;
  4. 随着测试数量级越大,性能差异的比例越趋于稳定。

综上所述,影响反射性能的主要原因是这两个方法:Class.getMethodClass.getDeclaredField

反射如何影响性能

由于测试的这四个方法最终调用的都是native方法,无法进一步跟踪。猜测应该是和在程序运行时操作class有关:

  1. 比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?主要是这一系列判断条件导致了反射耗时;
  2. 也有可能是因为调用natvie方法,需要使用JNI接口,导致了性能问题。参照Log.javaSystem.out.println都是调用native方法,重复调用多次耗时很明显。

如何避免反射导致的性能问题

  • 不要过于频繁地、大量地使用反射
  • 通过 反射直接操作属性 会比 反射调用方法间接操作属性 快很多,如果要使用反射的话,应该优先采用 反射直接操作属性 的方式。

其他问题

  • 丧失了编译时类型检查的好处,包含异常检查;
  • 执行反射访问所需要的代码非常笨拙和冗长;
  • 性能损失;
  • 对性能敏感的接口尽量不使用反射或者少使用反射

参考:

Java 反射到底慢在哪?


http://www.niftyadmin.cn/n/1487494.html

相关文章

美图

转载于:https://blog.51cto.com/leedlove/23439

Java-Thread.sleep(0)作用

文章目录CPU调度算法时间片抢占式Thread.sleep(n)Thread.sleep(0)CPU调度算法 时间片 Unix系统使用时间片算法。 所有的进程排成一个队列&#xff0c;操作系统按照他们的顺序&#xff0c;给每个进程分配一段时间&#xff0c;即该进程允许运行的时间。如果在时间片结束时进程…

mycat实现简单的mysql集群负载均衡

什么是mycat呢&#xff1f; 简单理解为一个MySQL中间件&#xff0c;它支持分流、基于心跳的自动故障切换&#xff0c;支持读写分离&#xff0c;支持mysql主从&#xff0c;基于Nio管理线程的高并发… 详见官网&#xff1a;http://www.mycat.io/ 为什么需要mysql集群&#xff1f…

【Java开源模板引擎】

【Java开源模板引擎】 http://www.open-open.com/21.htm 转载于:https://www.cnblogs.com/sharewind/archive/2007/04/24/725246.html

Java-热加载实现

文章目录什么是热加载热加载 VS 热部署部署方式实现原理使用场景准备Java类加载机制加载验证准备解析初始化如何实现热加载自定义类加载器为啥需要自定义类加载器如何自定义类加载器定时监控类修改优化解决方案什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生…

Parsing error: Unexpected token :

如果你创建的是Vue3的项目还报这个错误的话&#xff0c;很可能就是.eslintrc.js配置的问题。 我为了省事把vue2项目的.eslintrc.js复制到vue3的项目了。。 .eslintrc.js module.exports {root: true,env: {node: true,},extends: [plugin:vue/vue3-essential,eslint:recomm…

字体与字符集的关系

字体就是字的样子&#xff0c;是几何形状信息。字符集是一个映射表。一个字符是用二进制数字表示的&#xff0c;但具体是什么字符&#xff0c;是根据字符集决定的。打个比方&#xff0c;字符集就像过去的电报编码表&#xff0c;每个字都有个编号。发送电报的时候是发送的一堆数…

梳理caffe代码common(八)

因为想梳理data_layer的过程。整理一半发现有几个很重要的头文件就是题目列出的这几个&#xff1a; 追本溯源&#xff0c;先从根基開始学起。这里面都是些什么鬼呢&#xff1f; common类 命名空间的使用&#xff1a;google、cv、caffe{boost、std}。然后在项目中就能够任意使用…