我对一段代码的评论风格如下:

Iterable<String> upperCaseNames = Iterables.transform(
    lowerCaseNames, new Function<String, String>() {
        public String apply(String input) {
            return input.toUpperCase();
        }
    });

该人员说,每次浏览此代码时,我都会实例化此匿名Function类,而我应该在一个静态变量中有一个实例:
static Function<String, String> toUpperCaseFn =
    new Function<String, String>() {
        public String apply(String input) {
            return input.toUpperCase();
        }
    };
...
Iterable<String> upperCaseNames =
    Iterables.transform(lowerCaseNames, toUpperCaseFn);

从表面上看,这是有道理的。多次实例化一个类必须浪费内存,对吗?

另一方面,人们在代码中间实例化匿名类,就像没有明天一样,对于编译器来说,优化它是微不足道的。

这是一个有效的问题吗?

最佳答案

关于热点JVM优化的有趣事实,如果实例化未传递到当前方法之外的对象,则JVM将在字节码级别执行优化。
通常,堆栈分配与公开内存模型的语言(例如C++)相关联。您无需在C++中使用delete堆栈变量,因为退出范围时它们会自动释放。这与堆分配相反,堆分配要求您在使用完指针后将其删除。
在Hot JVM中,将分析字节码以确定对象是否可以“转义”线程。有three levels of escape:

  • 没有转义-该对象仅在创建它的方法/范围内使用,并且该对象不能在线程外访问。
  • Local/Arg转义-该对象由创建它的方法返回或传递给它调用的方法,但是这些方法都不会将该对象放置在线程外部可以访问的位置。
  • 全局转义-将对象放置在可以在另一个线程中访问的位置。

  • 这基本上类似于问题:1)是否将其传递给另一个方法或将其返回,以及2)是否将其与附加到GC根目录的内容(如ClassLoader或存储在static字段中的内容)相关联?
    在您的特定情况下,匿名对象将被标记为“本地转义”,这仅意味着该对象上的所有锁(读取:使用synchronized)都将被优化。 (为什么要在另一个线程中永远不会使用的东西上进行同步?)这与“无转义”不同,后者将在堆栈上进行分配。重要的是要注意,此“分配”与堆分配不同。它真正要做的是为非转义对象内的所有变量在堆栈上分配空间。如果在no-escape对象内有3个字段intStringMyObject,则将分配三个堆栈变量:intString引用和MyObject引用-MyObject实例本身仍将存储在堆中,除非它也被分析为“无法逃脱”。然后优化对象分配,并且构造函数/方法将使用本地堆栈变量而不是堆变量运行。
    话虽这么说,对我来说听起来像是过早的优化。除非后来证明该代码很慢并且会导致性能问题,否则您不应该采取任何措施来降低其可读性。对我来说,这段代码可读性很强,我将不理会它。当然,这完全是主观的,但是“性能”不是更改代码的充分理由,除非它与实际运行时间有关。通常,过早的优化会导致难以维护的代码具有最小的性能优势。
    Java 8+和Lambdas
    如果分配匿名实例仍然困扰您,建议您将Lambdas转换为单一抽象方法(SAM)类型使用。 Lambda评估是使用invokedynamic执行的,该实现最终在第一次调用时仅创建Lambda的单个实例。可以找到更多详细信息in my answer herethis answer here。对于非SAM类型,您仍将需要分配一个匿名实例。在大多数用例中,这里的性能影响可以忽略不计,但是IMO,这种方式更具可读性。
    引用
  • Escape analysis(wikipedia.org)
  • HotSpot逃逸分析14 | 11 | 8(oracle.com)
  • What is a 'SAM type' in Java?(stackoverflow.com)
  • Why are Java 8 lambdas invoked using invokedynamic?(stackoverflow.com)
  • 09-10 08:48
    查看更多