Java反射性能探讨

反射是否影响性能

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

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;
    }
}
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做如下改造:

/**
 * 反射操作方法总耗时
 * @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 反射到底慢在哪?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值