反射优化-创建对象篇

作为一个Java Coder,在开发过程中或多或少会用到反射,如动态创建指定泛型对象等,看着高大上,但是反射效率上着实有些感人,下面,将通过创建对象篇和方法执行篇来看看,在使用反射的时候,我们能怎么去优化它。

反射慢之初体验

  1. 我们先新建一个普通的类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package tech.zhaojian.domain;

    public class TestUser {
    private long id;
    private String name;

    public TestUser() {
    }

    public TestUser(long id, String name) {
    this.id = id;
    this.name = name;
    }

    //有参函数
    public void say(String words) {
    //System.out.println("the user say:" + words);
    }

    //无参函数
    public void walk() {

    }

    public long getId() {
    return id;
    }

    public void setId(long id) {
    this.id = id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return "TestUser{" +
    "id=" + id +
    ", name='" + name + '\'' +
    '}';
    }
    }
  2. 使用普通的new的方式和反射的方式创建对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Test
    /**
    * 通过正常new的方式创建对象
    * 平均耗时:5ms
    */
    public void createInNormal() {
    for (int i = 0; i < 1000000; i++) {
    new TestUser();
    }
    }

    @Test
    /**
    * 通过反射的方式创建对象
    * 平均耗时:800ms
    */
    public void createByReflect() throws Exception {
    for (int i = 0; i < 1000000; i++) {
    Class.forName("tech.zhaojian.domain.TestUser").newInstance();
    }
    }

    上面是调用无参构造函数的时候的效率对比,明显慢了很多,可想而知反射效率是比较感人的,那我们再试试有参构造会慢多少。

  3. 使用普通的new的方式和反射的方式调用有参构造

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Test
    /**
    * 通过正常new的方式创建对象
    * 平均耗时:50ms
    */
    public void createArgsInNormal() {
    for (int i = 0; i < 1000000; i++) {
    new TestUser(i,"hello");
    }
    }

    @Test
    /**
    * 通过反射的方式创建对象
    * 平均耗时:1300ms
    */
    public void createArgsByReflect() throws Exception {
    for (int i = 0; i < 1000000; i++) {
    Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
    Constructor<?> constructor = testUserClazz.getDeclaredConstructor(new Class[]{long.class,String.class});
    constructor.newInstance(i, "hello");
    }
    }

    还是慢了很多,但是整体效果上,但是差距没有无参构造那么明显。

    通过上面的试验,我们得出了如下图表:

    操作 平均耗时/ms
    无参new 5
    无参反射 800
    有参new 50
    有参反射 1300

优化反射

  1. 缓存Class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    /**
    * 通过反射缓存优化的方式创建对象,由此可见Class.forName("xxx")方法比较耗时
    * 平均耗时:50ms
    */
    public void createByReflectOptimize() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
    for (int i = 0; i < 1000000; i++) {
    testUserClazz.newInstance();
    }
    }

    咦,无参反射的耗时降了一个量级,800ms变成了50ms。通过代码我们可以发现,是Class.forName这个方法比较耗时,它实际上调用了一个本地方法,通过这个方法来要求JVM查找并加载指定的类。所以我们在项目中使用的时候,可以把Class.forName返回的Class对象缓存起来,下一次使用的时候直接从缓存里面获取,这样就极大的提高了获取Class的效率。同理,在我们获取Constructor、Method等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。

  2. 使用ReflectASM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    /**
    * 通过ReflectASM包的方式创建对象
    * 平均耗时:20ms
    */
    public void invokeByReflectASM(){
    ConstructorAccess<TestUser> access = ConstructorAccess.get(TestUser.class);
    for (int i = 0; i < 1000000; i++) {
    access.newInstance();
    }
    }

    高性能反射工具包ReflectASM,是通过字节码生成的方式来实现的反射机制,这样的反射能更进一步提升Java反射的性能。

源码可到 https://github.com/jamchiu/reflect-optimize 下载

参考:

如何提高使用Java反射的效率?

reflectasm