简体   繁体   English

重复实例化匿名类是浪费吗?

[英]Is repeatedly instantiating an anonymous class wasteful?

I had a remark about a piece of code in the style of:我对一段代码的评论是这样的:

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

The person said that every time I go through this code, I instantiate this anonymous Function class, and that I should rather have a single instance in, say, a static variable:该人说,每次我浏览这段代码时,我都会实例化这个匿名函数类,并且我宁愿在静态变量中拥有一个实例:

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

On a very superficial level, this somehow makes sense;在非常肤浅的层面上,这在某种程度上是有道理的; instantiating a class multiple times has to waste memory or something, right?多次实例化一个类会浪费内存什么的,对吧?

On the other hand, people instantiate anonymous classes in middle of the code like there's no tomorrow, and it would be trivial for the compiler to optimize this away.另一方面,人们在代码中间实例化匿名类,就像没有明天一样,编译器优化它是微不足道的。

Is this a valid concern?这是一个有效的担忧吗?

Fun fact about Hot Spot JVM optimizations, if you instantiate an object that isn't passed outside of the current method, the JVM will perform optimizations at the bytecode level.关于 Hot Spot JVM 优化的有趣事实,如果您实例化一个未在当前方法之外传递的对象,JVM 将在字节码级别执行优化。

Usually, stack allocation is associated with languages that expose the memory model, like C++.通常,堆栈分配与公开内存模型的语言(如 C++)相关联。 You don't have to delete stack variables in C++ because they're automatically deallocated when the scope is exited.您不必在 C++ 中delete堆栈变量,因为它们会在退出作用域时自动释放。 This is contrary to heap allocation, which requires you to delete the pointer when you're done with it.这与堆分配相反,堆分配要求您在完成后删除指针。

In the Hot Spot JVM, the bytecode is analyzed to decide if an object can "escape" the thread.在 Hot Spot JVM 中,分析字节码以确定对象是否可以“逃脱”线程。 There are three levels of escape : 逃生分为三个级别

  1. No escape - the object is only used within the method/scope it is created, and the object can't be accessed outside the thread.无转义 - 对象仅在其创建的方法/范围内使用,并且无法在线程外访问该对象。
  2. Local/Arg escape - the object is returned by the method that creates it or passed to a method that it calls, but none of those methods will put that object somewhere that it can be accessed outside of the thread. Local/Arg 转义 - 对象由创建它的方法返回或传递给它调用的方法,但这些方法都不会将该对象放在可以在线程外部访问的某个地方。
  3. Global escape - the object is put somewhere that it can be accessed in another thread.全局转义 - 对象被放置在可以在另一个线程中访问的某个地方。

This basically is analogous to the questions, 1) do I pass it to another method or return it, and 2) do I associate it with something attached to a GC root like a ClassLoader or something stored in a static field?这基本上类似于问题,1)我是将它传递给另一个方法还是返回它,2)我是否将它与附加到 GC 根的东西(如ClassLoader或存储在static字段中的东西)相关联?

In your particular case, the anonymous object will be tagged as "local escape", which only means that any locks (read: use of synchronized ) on the object will be optimized away.在您的特定情况下,匿名对象将被标记为“本地转义”,这仅意味着该对象上的任何锁(读取:使用synchronized )都将被优化掉。 (Why synchronize on something that won't ever be used in another thread?) This is different from "no escape", which will do allocation on the stack. (为什么在另一个线程中永远不会使用的东西上进行同步?)这与“无转义”不同,后者在堆栈上进行分配。 It's important to note that this "allocation" isn't the same as heap allocation.需要注意的是,这种“分配”与堆分配不同。 What it really does is allocates space on the stack for all the variables inside the non-escaping object.它真正做的是在堆栈上为非转义对象内的所有变量分配空间。 If you have 3 fields, int , String , and MyObject inside the no-escape object, then three stack variables will be allocated: an int , a String reference, and a MyObject reference – the MyObject instance itself will still be stored in heap unless it is also analyzed to have "no escape".如果在 no-escape 对象中有 3 个字段, intStringMyObject ,那么将分配三个堆栈变量:一个int 、一个String引用和一个MyObject引用MyObject实例本身仍将存储在堆中,除非也被分析为“无处可逃”。 The object allocation is then optimized away and constructors/methods will run using the local stack variables instead of heap variables.然后对象分配被优化掉,构造函数/方法将使用本地堆栈变量而不是堆变量运行。

That being said, it sounds like premature optimization to me.话虽如此,对我来说这听起来像是过早的优化。 Unless the code is later proven to be slow and is causing performance problems, you shouldn't do anything to reduce its readability.除非后来证明代码很慢并且导致性能问题,否则您不应该做任何事情来降低其可读性。 To me, this code is pretty readable, I'd leave it alone.对我来说,这段代码可读性很强,我不理会它。 This is totally subjective, of course, but "performance" is not a good reason to change code unless it has something to do with its actual running time.当然,这完全是主观的,但是“性能”不是更改代码的好理由,除非它与实际运行时间有关。 Usually, premature optimization results in code that's harder to maintain with minimal performance benefits.通常,过早的优化会导致代码难以维护,而性能优势却很小。

Java 8+ and Lambdas Java 8+ 和 Lambda

If allocating anonymous instances still bothers you, I recommend switching to using Lambdas for single abstract method (SAM) types.如果分配匿名实例仍然困扰您,我建议您改用 Lambdas 来处理单个抽象方法 (SAM) 类型。 Lambda evaluation is performed using invokedynamic , and the implementation ends up creating only a single instance of a Lambda on the first invocation. Lambda 评估是使用invokedynamic执行的,并且实现最终在第一次调用时仅创建 Lambda 的单个实例。 More details can be found in my answer here and this answer here .更多细节可以在我的答案here这个答案here中找到 For non-SAM types, you will still need to allocate an anonymous instance.对于非 SAM 类型,您仍然需要分配一个匿名实例。 The performance impact here will be negligible in most use cases, but IMO, it's more readable this way.在大多数用例中,这里的性能影响可以忽略不计,但 IMO,这种方式更具可读性。

References参考文献

Short answer: No - don't worry.简短回答:不 - 别担心。

Long answer: it depends how frequently you're instantiating it.长答案:这取决于您实例化它的频率。 If in a frequently-called tight loop, maybe - though note that when the function is applied it calls String.toUpperCase() once for every item in an Iterable - each call presumably creates a new String , which will create far more GC churn.如果在经常调用的紧密循环中,也许 - 尽管请注意,当应用该函数时,它为Iterable每个项目调用String.toUpperCase()一次 - 每次调用可能会创建一个新的String ,这将产生更多的 GC 流失。

"Premature optimization is the root of all evil" - Knuth “过早的优化是万恶之源”——Knuth

Found this thread: Java anonymous class efficiency implications , you may find it interesting发现这个线程: Java 匿名类效率影响,你可能会觉得很有趣

Did some micro-benchmarking.做了一些微基准测试。 The micro-benchmark was a comparison between: instantiating an (static inner) class per loop iteration, instantiating a (static inner) class once and using it in the loop, and the two similar ones but with anonymous classes.微基准测试比较了:每次循环迭代实例化一个(静态内部)类,实例化一次(静态内部)类并在循环中使用它,以及两个相似但使用匿名类的类。 For the micro benchmarking the compiler seemed to extract the anonymous class out of loops and as predicted, promoted the anonymous class to an inner class of the caller.对于微基准测试,编译器似乎从循环中提取匿名类,并如预测的那样,将匿名类提升为调用者的内部类。 This meant all four methods were indistinguishable in speed.这意味着所有四种方法在速度上都没有区别。 I also compared it to an outside class and again, same speed.我还将它与外部课程进行了比较,并且速度相同。 The one with anonymous classes probably took ~128 bits of space more带有匿名类的那个可能多占用了大约 128 位的空间

You can check out my micro-benchmark at http://jdmaguire.ca/Code/Comparing.java & http://jdmaguire.ca/Code/OutsideComp.java .您可以在http://jdmaguire.ca/Code/Comparing.javahttp://jdmaguire.ca/Code/OutsideComp.java 上查看我的微基准测试。 I ran this on various values for wordLen, sortTimes, and listLen.我在 wordLen、sortTimes 和 listLen 的各种值上运行了这个。 As well, the JVM is slow to warm-up so I shuffled the method calls around.同样,JVM 预热很慢,所以我调整了方法调用。 Please don't judge me for the awful non-commented code.请不要因为糟糕的未注释代码而评判我。 I program better than that in RL.我在 RL 中编程比在 RL 中更好。 And Microbenching marking is almost as evil and useless as premature optimization.微基准测试几乎和过早优化一样邪恶和无用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM