作为一个Java Coder,在开发过程中或多或少会用到反射,如动态创建指定泛型对象等,看着高大上,但是反射效率上着实有些感人,下面,将通过创建对象篇和方法执行篇来看看,在使用反射的时候,我们能怎么去优化它。
反射慢之初体验
我们先新建一个普通的类
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
48package 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;
}
public String toString() {
return "TestUser{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}使用普通的new的方式和反射的方式创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 通过正常new的方式创建对象
* 平均耗时:5ms
*/
public void createInNormal() {
for (int i = 0; i < 1000000; i++) {
new TestUser();
}
}
/**
* 通过反射的方式创建对象
* 平均耗时:800ms
*/
public void createByReflect() throws Exception {
for (int i = 0; i < 1000000; i++) {
Class.forName("tech.zhaojian.domain.TestUser").newInstance();
}
}上面是调用无参构造函数的时候的效率对比,明显慢了很多,可想而知反射效率是比较感人的,那我们再试试有参构造会慢多少。
使用普通的new的方式和反射的方式调用有参构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 通过正常new的方式创建对象
* 平均耗时:50ms
*/
public void createArgsInNormal() {
for (int i = 0; i < 1000000; i++) {
new TestUser(i,"hello");
}
}
/**
* 通过反射的方式创建对象
* 平均耗时: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
优化反射
缓存Class
1
2
3
4
5
6
7
8
9
10
11
/**
* 通过反射缓存优化的方式创建对象,由此可见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等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。
使用ReflectASM
1
2
3
4
5
6
7
8
9
10
11
/**
* 通过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 下载
参考: