上一篇博文主要讲了通过new和通过反射的方式创建对象实例的性能对比,以及通过反射创建对象实例的优化,这篇,作为兄弟篇,我们也从性能对比和优化上来讲讲方法执行。
domain类会使用到创建对象篇中的TestUser。
性能对比
在TestUser类中的两个方法,由于实验结果中对两个函数的调用实验结果几乎保持了一致,所以我们只拿一个方法在这里做解析
1
2
3
4
5
6
7
8
9//有参函数
public void say(String words) {
//System.out.println("the user say:" + words);
}
//无参函数
public void walk() {
}使用普通的方式和反射的方式调用方法
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/**
* 通过正常调用方式执行方法
* 平均耗时:5ms
*/
public static void invokeInNormal() {
long startTime = System.currentTimeMillis();
//start
TestUser testUser = new TestUser();
for (int i = 0; i < INVOKE_TIME; i++) {
testUser.walk();
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeInNormal",endTime - startTime);
}
/**
* 通过反射的方式执行方法
* 平均耗时:350ms
*/
public static void invokeByReflect() throws Exception {
long startTime = System.currentTimeMillis();
//start
Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
TestUser testUser = (TestUser) testUserClazz.newInstance();
Method walk = testUserClazz.getMethod("walk");
for (int i = 0; i < INVOKE_TIME; i++) {
walk.invoke(testUser);
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeByReflect",endTime - startTime);
}反射明显要慢了非常多,但是这里的耗时面包含了对象实例化的,而且
Class.forName
函数真的很慢,为了公平起见,我们只比较方法执行效率上是不是也真的是慢很多。更加单纯的比较方法执行效率
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/**
* 通过正常调用方式执行方法
* 平均耗时:5ms
*/
public static void invokeInNormal() {
TestUser testUser = new TestUser();
long startTime = System.currentTimeMillis();
//start
for (int i = 0; i < INVOKE_TIME; i++) {
testUser.walk();
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeInNormal",endTime - startTime);
}
/**
* 通过反射的方式执行方法
* 平均耗时:300ms
*/
public static void invokeByReflect() throws Exception {
Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
TestUser testUser = (TestUser) testUserClazz.newInstance();
Method walk = testUserClazz.getMethod("walk");
long startTime = System.currentTimeMillis();
//start
for (int i = 0; i < INVOKE_TIME; i++) {
walk.invoke(testUser);
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeByReflect",endTime - startTime);
}上面的代码我们将startTime移到了for循环的前一句,但是实验结果还是比较感人,反射的方式比原来快了50ms,但是还是远远不如普通的调用方式。
通过上面的试验,我们得出了如下图表:
操作 平均耗时/ms 普通调用 5 反射调用 300 - 350
反射优化
设置
setAccessible=true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 通过反射 setAccessible=true 方式执行方法
* 平均耗时:300ms
*/
public static void invokeByReflectOptimize() throws Exception {
long startTime = System.currentTimeMillis();
//start
Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
TestUser testUser = (TestUser) testUserClazz.newInstance();
Method walk = testUserClazz.getMethod("walk");
walk.setAccessible(true);
for (int i = 0; i < INVOKE_TIME; i++) {
walk.invoke(testUser);
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeByReflectOptimize",endTime - startTime);
}这里我们反射调用walk方法1亿次,在调用了
setAccessible(true)
后,发现快了一些,但是也没有快到其他博主说的一倍那么多。查看API可以了解到,jdk在设置获取字段,调用方法的时候会执行安全访问检查,而此类操作会比较耗时,所以通过setAccessible(true)
的方式可以关闭安全检查,从而提升反射效率。使用ReflectASM怎么更慢了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 通过ReflectASM包的方式执行方法
* 平均耗时:800ms
*/
public static void invokeByReflectASM() throws Exception {
long startTime = System.currentTimeMillis();
//start
Class<?> testUserClazz = Class.forName("tech.zhaojian.domain.TestUser");
TestUser testUser = (TestUser) testUserClazz.newInstance();
MethodAccess access = MethodAccess.get(TestUser.class);
for (int i = 0; i < INVOKE_TIME; i++) {
access.invoke(testUser, "walk");
}
//end
long endTime = System.currentTimeMillis();
System.out.printf("%30s : %-6dms\n","invokeByReflectASM",endTime - startTime);
}理论上来说,基于字节码技术的ReflectASM包执行起来应该会比普通反射要快一些,但是反复试验后发现,比不设置
setAccessible(true)
的反射方式调用还要慢很多,这个有待进一步考究了。
源码可到 https://github.com/jamchiu/reflect-optimize 下载
参考:
[reflectasm](