【问题标题】:Performance of reflection: quality byte code in JVM反射的表现:JVM中的优质字节码
【发布时间】:2013-02-05 09:22:36
【问题描述】:

编辑 2: 具有完全object-oriented 实现的程序是否提供高性能?大多数framework 都是用它的全部力量编写的。然而,reflection 也被大量用于实现它,例如AOPdependency injection。使用reflection会在一定程度上影响性能。

那么,使用reflection 是一种好习惯吗?是否有一些替代编程语言结构的反射的方法? reflection应该使用到什么程度?

【问题讨论】:

  • 首先,几乎每个程序员都会编写 Java(或在 JVM 上运行的另一种语言)来编译为字节码。我们也不要忘记,真正的性能来自 Hotspot,它使用动态分析来识别代码,以便进行大量优化的即时编译。
  • 还有一件事——当动态绑定总是(或几乎总是)针对特定的类或方法时,Hotspot 会发现,并对其进行优化,使其与动态绑定一样快(如果 Hotspot 内联被调用的方法,则更快) .所以不要太在意绑定类型。
  • 至于实现性能:1) 从不及早优化 2) 在您认为需要性能时进行配置 3) 优化您的分析数据显示的速度很慢。性能通常与您选择的算法或数据结构有关,或者与开发人员使用错误的习惯用法(例如 Java 中的字符串 + 字符串 + 字符串)或不使用/误用线程有关。
  • 我认为这不是一个真正可以回答的问题。它包含许多模糊的问题,我会投票支持将其关闭为“不是一个真正的问题”。不幸的是我不能因为赏金......所以请尝试重写你的问题,只包含一个可回答的问题。
  • -1 因为这个问题到处都是。

标签: java reflection coding-style bytecode


【解决方案1】:

反思本身和本质上都是缓慢的。有关详细信息,请参阅this question。 这是由几个原因造成的。 Jon Skeet explains it nicely:

检查是否有无参数构造函数检查可访问性 无参数构造函数检查调用者是否有权访问 完全使用反射计算(在执行时)有多少空间 需要分配调用到构造函数代码中(因为它不会 事先知道构造函数是空的)

基本上,反射必须在调用之前执行所有上述步骤,而普通方法调用所要做的要少得多。

用于实例化 B 的 JITted 代码非常轻量级。 基本上它需要分配足够的内存(这只是 除非需要 GC,否则递增指针),仅此而已 - 真的没有要调用的构造函数代码;我不知道是否 JIT 是否跳过它,但无论哪种方式都没有太多工作要做。

话虽如此,在很多情况下,Java 的动态性不足以满足您的需求,而反射提供了一种简单而干净的替代方案。考虑以下场景:

  1. 您有大量代表各种项目的类,即 CarBoatHouse
  2. 它们都扩展/实现了同一个类:LifeItem
  3. 您的用户输入了 3 个字符串之一,“Car”、“Boat”或“House”。
  4. 您的目标是根据参数访问 LifeItem 的方法。
  5. 首先想到的方法是构建一个 if/else 结构,并构建想要的LifeItem。但是,这不是非常可扩展的,并且一旦您拥有数十个 LifeItem 实现,就会变得非常混乱。

反射在这里可以提供帮助:它可用于根据名称动态构造 LifeItem 对象,因此“汽车”输入将被分派到 Car 构造函数。突然间,原本可能有数百行 if/else 代码的东西变成了一条简单的反射线。由于引入了带有字符串的 switch 语句,后一种情况在 Java 7+ 平台上不会那么有效,但即便如此,我也希望避免使用具有数百种情况的 switch。在大多数情况下,清洁度之间的区别如下:

没有反射:

public static void main(String[] args) {
   String input = args[0];
   if(input.equals("Car"))
      doSomething(new Car(args[1]));
   else if(input.equals("Boat"))
      doSomething(new Boat(args[1]));
   else if (input.equals("House"))
      doSomething(new House(args[1]));
   ... // Possibly dozens more if/else statements
}

而通过利用反射,它可以变成:

public static void main(String[] args) {    
   String input = args[0];
   try {
      doSomething((LifeItem)Class.forName(input).getConstructor(String.class).newInstance(args[1]));
   } catch (Exception ie) {
      System.err.println("Invalid input: " + input);
   }  
}

就个人而言,我会说后者比第一个更整洁、更简洁、更易于维护。最后,这是个人喜好,但这只是反射有用的众多情况之一。

此外,在使用反射时,您应该尝试缓存尽可能多的信息。换句话说,使用简单、合乎逻辑的东西,比如如果你能提供帮助,就不要到处调用get(Declared)Method:而是将它存储在一个变量中,这样你就不会在想使用它时重新获取引用的开销。

所以这是赞成和反对反思的两个极端。总结一下,如果反射提高了代码的可读性(就像在呈现的场景中那样),那么一定要去做。如果你这样做了,只需考虑减少 get* 反射调用的数量:这些是最容易修剪的。

【讨论】:

    【解决方案2】:

    虽然反射比“传统代码”最昂贵,但过早优化是万恶之源。从长达十年的经验证据来看,我假设通过反射调用的方法几乎不会影响性能,除非它是从重循环中调用的,即使如此,反射也有一些性能增强:

    某些反射操作,特别是 Field、Method.invoke()、 Constructor.newInstance() 和 Class.newInstance(),已经 重写以获得更高的性能。反射调用和 实例化速度比以前的版本快几倍 Enhancements in J2SDK 1.4 -

    请注意,上面没有提到方法查找(即Class.getMethod),并且选择正确的Method对象通常需要额外的步骤,例如遍历类层次结构同时询问“声明的方法”以防它不是公共的) ,所以我倾向于尽可能将找到的方法保存在合适的地图中,这样下一次的成本就只有 Map.get() 和 Method.invoke() 的成本。我想任何编写良好的框架都可以正确处理这个问题。

    还应该考虑如果使用反射,某些优化是不可能的(例如方法内联或转义分析。Java HotSpot™ Virtual Machine Performance Enhancements)。但这并不意味着必须不惜一切代价避免反射。

    但是,我认为使用反射的决定应该基于其他标准,例如代码可读性、可维护性、设计实践等。在你自己的代码中使用反射时(而不是使用内部使用反射的框架),一种将编译时错误转换为运行时错误的风险,这些错误更难调试。在某些情况下,可以将反射调用替换为传统的 OOP 模式,例如命令或抽象工厂。

    【讨论】:

      【解决方案3】:

      我可以举一个例子(但是很抱歉,我不能给你看测试结果,因为那是几个月前的事了)。我编写了一个 XML 库(面向自定义项目),它用类 + 注释替换了一些旧的 DOM 解析器代码。我的代码是原始代码的一半。我做了测试,是的,反射更昂贵,但并不多(大约 14-15 秒的执行时间中的 0.3 秒(损失约为 2%))。在不经常执行代码的地方,可以使用反射,但性能损失很小。

      此外,我确信我的代码可以改进以获得更好的性能。

      所以,我建议以下提示:

      1. 如果可以以美观、紧凑和简洁的方式进行反射,请使用反射;
      2. 如果您的代码将被多次执行,请不要使用反射;
      3. 如果您需要将大量信息从另一个源(例如 XML 文件)投射到 Java 应用程序,请使用反射;
      4. 反射和注释的最佳用法是代码只执行一次(预加载器)。

      【讨论】:

        猜你喜欢
        • 2012-12-26
        • 1970-01-01
        • 1970-01-01
        • 2013-03-19
        • 1970-01-01
        • 2010-11-15
        • 1970-01-01
        • 2011-10-10
        • 1970-01-01
        相关资源
        最近更新 更多