Java反思:为什么这么慢?

我总是避免Javareflection,因为它的缓慢声誉。 我在当前项目的devise中达到了一个地步,能够使用它会使我的代码更具可读性和优雅性,所以我决定放弃它。

我简直惊讶的差异,我注意到有时几乎100倍的运行时间。 即使在这个简单的例子中,它只是实例化一个空的类,这是令人难以置信的。

class B { } public class Test { public static long timeDiff(long old) { return System.currentTimeMillis() - old; } public static void main(String args[]) throws Exception { long numTrials = (long) Math.pow(10, 7); long millis; millis = System.currentTimeMillis(); for (int i=0; i<numTrials; i++) { new B(); } System.out.println("Normal instaniation took: " + timeDiff(millis) + "ms"); millis = System.currentTimeMillis(); Class<B> c = B.class; for (int i=0; i<numTrials; i++) { c.newInstance(); } System.out.println("Reflecting instantiation took:" + timeDiff(millis) + "ms"); } } 

所以真的,我的问题是

  • 为什么这很慢? 有什么我做错了吗? (即使是上面的例子也说明了这个区别)。 我很难相信它比真正的实例化要慢100倍。

  • 有什么其他的东西可以更好地用于处理代码作为数据(记住我现在卡在Java)

您的testing可能有缺陷。 虽然JVM可能会优化正常的实例,但是不能对reflection用例进行优化 。

对于那些想知道时代的人来说,我添加了一个热身阶段,并使用一个数组来维护创build的对象(更像一个真正的程序可能做的)。 我在我的OSX,jdk7系统上运行testing代码,得到这个:

反映实例化了:5180ms
正常立即采取:2001ms

修改testing:

 public class Test { static class B { } public static long timeDiff(long old) { return System.nanoTime() - old; } public static void main(String args[]) throws Exception { int numTrials = 10000000; B[] bees = new B[numTrials]; Class<B> c = B.class; for (int i = 0; i < numTrials; i++) { bees[i] = c.newInstance(); } for (int i = 0; i < numTrials; i++) { bees[i] = new B(); } long nanos; nanos = System.nanoTime(); for (int i = 0; i < numTrials; i++) { bees[i] = c.newInstance(); } System.out.println("Reflecting instantiation took:" + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms"); nanos = System.nanoTime(); for (int i = 0; i < numTrials; i++) { bees[i] = new B(); } System.out.println("Normal instaniation took: " + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms"); } } 

由于一些显而易见的原因,反思缓慢:

  1. 编译器不能做任何优化,因为它对于你在做什么没有真正的想法。 这也可能适用于JIT
  2. 所有被调用/创build的东西都必须被发现 (即按名称查找类,查找匹配等方法)
  3. 参数需要通过装箱/取消装箱,打包到数组中,包装在InvocationTargetExceptionExceptions和重新抛出等。
  4. Jon Skeet在这里提到的所有处理。

只是因为某些东西慢了100倍,并不意味着它的速度太慢了,因为假设reflection是devise程序的“正确途径”。 例如,我想像IDE大量使用reflection,而从性能angular度来说,我的IDE大多数情况下是可以的。

毕竟, parsingXML或访问数据库 相比 ,reflection开销可能会变得微不足道

还有一点需要记住的是, 微观基准是一个臭名昭着的有缺陷的机制,用于确定事物在实践中的速度有多快 。 和Tim Bender的评论一样 ,JVM需要时间来“热身”,JIT可以即时重新优化代码热点。

用于实例化B的JITted代码非常轻便。 基本上它需要分配足够的内存(这只是增加了一个指针,除非需要GC),就是这样 – 没有任何构造函数代码可以调用。 我不知道JIT是否跳过它,但是不pipe怎么样,没有太多的事情要做。

比较一下那些反思必须做的事情:

  • 检查是否有无参数的构造函数
  • 检查无参数构造函数的可访问性
  • 检查调用者是否有权使用reflection
  • 制定(在执行时间)需要分配多less空间
  • 调用构造函数代码(因为事先不知道构造函数是空的)

…可能还有其他我还没有想到的东西。

通常情况下,reflection不用于性能关键的环境; 如果你需要这样的dynamic行为,你可以使用像BCEL这样的东西。

看来,如果你使构造函数可访问,它将执行得更快。 现在只比其他版本慢10-20倍。

  Constructor<B> c = B.class.getDeclaredConstructor(); c.setAccessible(true); for (int i = 0; i < numTrials; i++) { c.newInstance(); } Normal instaniation took: 47ms Reflecting instantiation took:718ms 

如果你使用服务器虚拟机,它可以优化它,所以它只有3-4倍慢。 这是相当典型的performance。 Geo链接的文章是一个很好的阅读。

 Normal instaniation took: 47ms Reflecting instantiation took:140ms 

但是,如果使用-XX:+ DoEscapeAnalysis启用标量replace,则JVM能够优化常规实例化(它将为0-15毫秒),但reflection实例保持不变。

  • 初次引入时reflection非常缓慢,但在较新的JRE中已经大大加快
  • 不过,在内部循环中使用reflection可能不是一个好主意
  • 基于reflection的代码对基于JIT的优化的潜力很低
  • reflection主要用于连接松散耦合的组件,例如查找只有接口已知的具体类和方法:dependency injection框架,实例化JDBC实现类或XMLparsing器。 这些用途通常可以在系统启动时进行一次,所以小的低效率无论如何都不重要!

也许你会觉得这篇文章很有趣。

@Tim Bender的代码在我的机器上给出了这些结果(jdk_1.8_45,os_x 10.10,i7,16G):

Reflecting instantiation took:1139ms Normal instaniation took: 4969ms

所以在现代JVM上看来,reflection代码也会被优化。