【问题标题】:Why defining class as final improves JVM performance?为什么将类定义为 final 会提高 JVM 性能?
【发布时间】:2011-04-27 02:06:41
【问题描述】:

引用http://sites.google.com/site/gson/gson-design-document:

为什么 Gson 中的大多数类都标记为 决赛?

虽然 Gson 提供了一个相当 通过提供可扩展架构 可插拔序列化器和 反序列化器,Gson 类不是 专门设计为 可扩展的。提供非最终 类将允许用户 合法地扩展 Gson 类,并且 然后期望该行为起作用 所有后续修订。我们选择了 通过标记限制此类用例 类作为最终的,并等到一个 出现好的用例以允许 可扩展性。标记班级决赛 还有一个小小的好处是提供 额外的优化机会 到Java编译器和虚拟机。

为什么会这样? [如果我猜:JVM 知道类是最终的,它不维护方法覆盖表吗?还有其他原因吗?]

对性能有什么好处?

这是否适用于频率实例化的类(POJO?),或者可能适用于持有静态方法的类(实用程序类)?

定义为final的方法理论上也能提高性能吗?

有什么影响吗?

谢谢你, 马克西姆。

【问题讨论】:

  • 我敢肯定,如果您搜索 SO,您会找到答案;我记得几天前的一个。但无论如何,我建议您忽略最后一句话;该段的其余部分是重要的。
  • 我要求仅用于学习目的。想要一个链接,因为我还没有找到这样的链接(在询问之前进行了快速搜索)。
  • 同意,标记类final 的好处更多地在于记录它们的设计(或缺乏)以实现可扩展性而不是性能。 (另一方面,这不是@Maxim Veksler 所问的......)
  • 我知道没有相对健全的 JVM 会跳过 CHA(类层次结构分析),所以最终类没有任何改进。
  • 据我了解,是 JIT 对 final 而非 JVM 进行任何优化。 See this article for more details.

标签: java performance optimization jvm final


【解决方案1】:

虚拟(覆盖)方法通常是通过某种表(vtable)实现的,该表最终是一个函数指针。每个方法调用都有必须通过该指针的开销。当类被标记为 final 时,所有方法都不能被覆盖,并且不再需要使用表 - 这样更快。

某些 VM(如 HotSpot)可能会更智能地做事,并且知道方法何时被/未被覆盖并在适当时生成更快的代码。

Here 是关于 HotSpot 的一些更具体的信息。还有一些general 信息。

【讨论】:

    【解决方案2】:

    一篇来自 IBM developerWorks 的旧文章,显然不再但仍然在很大程度上相关,其中指出:

    普遍的看法是 声明类或方法 final 使编译器更容易 内联方法调用,但这 感知不正确(或在 至少,大大夸大了)。

    final 类和方法可以是 显着不便时 编程——它们限制了你的选择 用于重用现有代码和 扩展功能 现有的类。虽然有时一个 有充分的理由将课程定为最终课程, 例如为了强制不变性, 使用 final 的好处应该 胜过不便。 性能提升差不多 妥协总是一个不好的理由 面向对象的设计原则,以及 当性能增强是 小或不存在,这是一个坏的 确实是取舍。

    另见related answer on another question。还有equivalent question for .Net, discussed here。所以讨论,“Are final methods inlined?”在题为“What optimizations are going to be useless tomorrow”的问题中,这个问题出现在列表中。

    还要注意final 类与final 方法的效果之间存在纠缠不清。您可能肯定会为final 方法获得一些性能优势(同样,我没有很好的参考),因为它可以提示 JIT 进行内联否则无法做到(或不那么简单)。当你标记类final 时,你会得到同样的效果,这意味着所有的方法也突然变成了final。请注意,Sun/Oracle 人员声称 HotSpot 通常可以使用或不使用 final 关键字来执行此操作。拥有类本身final 是否有任何其他效果?

    作为参考,final methodsfinal classes 上的 JLS 链接。

    【讨论】:

    • 在非常古老的日子里确实如此。当我们把石头撞在一起得到一、零和零时。
    • @Thorbjørn: 把石头放在一起听起来比我看到的大多数软件开发更有效率...... ;-) 大概 HotSpot 做了所有正确的事情——我们可以假设所有的 Java 编译器和JVM 终于赶上了?
    • @andersoj,高性能 JVM 必须这样做才能获得足够的速度(换取内存)。像 JamVM 这样的小型 JVM 无法进行积极的优化以保持较小,因此它们无法获得那么快。
    • @Thorbjørn:澄清一​​下——您提到的时间/空间权衡是保留内联代码段的空间(与执行此代码的效率相比)?
    • @andersoj。不,这是 JVM 需要用于描述正在运行的程序的内部数据结构的空间。基本上,您可以记住的计算越多,您可以进行更高级的程序转换。
    【解决方案3】:

    不知道每个特定 JVM 的实现,我理论上会说,如果 JVM 知道指向对象的指针是指向最终类型的指针,它可以进行非虚拟函数调用(即直接与.indirect) 到成员函数(即,不通过函数指针间接),这可能会导致更快的执行。这也可能反过来导致内联的可能性。

    【讨论】:

    • Sun JVM 足够智能,如果没有加载子类,它会将基类的方法视为非虚拟方法。
    • 是的,但如果这样做,它还需要识别何时加载新的子类并强制重新编译对
    • (续 - 顺便说一句,我无法修复我的评论有多迟钝?)......被推断为最终的基类方法。 JVM 不可能跟踪每个这样的方法,因此它可能需要重新编译所有调用该基类方法的所有方法。
    【解决方案4】:

    将类标记为 final 允许在 JIT 阶段应用进一步的优化。

    如果您在非最终类上调用虚方法,您不知道正确的实现是在该类中定义的,还是您不知道的某个子类。

    但是,如果您有对最终类的引用,则您知道所需的具体实现。

    考虑:

    A extends B
    B extends C
    
    B myInstance = null;
    if(someCondition)
       myInstance = new B();
    else
       myInstance = new C();
    myInstance.toString();
    

    在这种情况下,JIT 无法知道是调用 C 的 toString() 实现还是 B 的 toString() 实现。但是,如果 B 被标记为 final,那么除了 B 之外的任何实现都不可能是正确的实现

    【讨论】:

    • 是否有文档表明 HotSpot 或任何其他主要 JVM 供应商利用了这一点?最近有没有人在生产代码中测量过这种效果?
    • JVM 确实知道是否存在子类,因为它会跟踪已加载的类。如果 B 和 C 从未加载,则它知道 A 的所有方法都可以直接调用。
    【解决方案5】:

    没有区别,这只是猜测。唯一有意义的情况是 String 等类,jvm 对它们的处理方式不同。

    【讨论】:

      猜你喜欢
      • 2010-12-09
      • 1970-01-01
      • 2012-10-03
      • 2015-07-16
      • 2015-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多