11

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?

user711413
  • 761
  • 5
  • 12
  • 1
    Object allocation in Java is extremely fast. And instantiating an anonymous class is no different from a "regular" class. As the anonymous class does not have any instance variables, hardly any memory is used (and the object itself might be allocated on the stack anyway - but I'm not sure about that). But that person is basically correct: instantiating objects in a loop *can* be expensive. But that has nothing to do with anonymous classes, that's true for instantiating any class. –  Oct 31 '13 at 23:31
  • I "waste" so many CPU cycles by "needlessly creating disposable classes". Then again, I write for desktops/servers and *I don't care if I could save a millisecond or two [overall]*. Instead, write the code so that it is easy to reason about - which *may* (or may not) mean "reusing" an instance and/or giving it a moniker - and then profile it. No profiled issue? No problem! – user2864740 Oct 31 '13 at 23:39
  • It's wasteful if it's wasteful. The fact that it's an anonymous class has nothing to do with it. – user207421 Oct 31 '13 at 23:41
  • @a_horse_with_no_name Your assumption is correct about the stack allocation, [see here](http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html) and see my answer. – Brian Oct 31 '13 at 23:46
  • I get that an anonymous class is like any other class, it's just that this type of code is used particularly often with anonymous classes (listeners, callbacks, etc.) Is there a collective name for these, like Single-Abstract-Method classes or something? – user711413 Oct 31 '13 at 23:52
  • @user2864740 My comment is addressed to the OP, not to you. – user207421 Nov 01 '13 at 00:38

3 Answers3

8

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.

Usually, stack allocation is associated with languages that expose the memory model, like C++. You don't have to delete stack variables in C++ because they're automatically deallocated when the scope is exited. 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. 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.
  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?

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. (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". 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

If allocating anonymous instances still bothers you, I recommend switching to using Lambdas for single abstract method (SAM) types. Lambda evaluation is performed using invokedynamic, and the implementation ends up creating only a single instance of a Lambda on the first invocation. More details can be found in my answer here and this answer here. For non-SAM types, you will still need to allocate an anonymous instance. The performance impact here will be negligible in most use cases, but IMO, it's more readable this way.

References

Brian
  • 17,079
  • 6
  • 43
  • 66
  • Hm. The link you posted about levels of escape has this text: "After escape analysis, the server compiler **eliminates scalar replaceable object allocations** and associated locks from generated code. The server compiler also eliminates locks for all non-globally escaping objects. It **does not replace a heap allocation with a stack allocation** for non-globally escaping objects." – user711413 Nov 01 '13 at 00:05
  • @user711413 Put it together in a haste and wasn't thinking clearly, sorry. I'll update my answer. – Brian Nov 01 '13 at 00:10
  • 2
    Actually, after inlining the code receiving that function, the function object is likely to turn into “No Escape” state (depending on what that method does). But this does not imply that there will be a stack allocation—since the object bears no state, there will be no allocation at all. Instead, the function’s logic will get inlined into the code of the invoked method (in this context). Still, when replacing it with a lambda expression, you’ll get a singleton, even if the object escapes. So Java 8 relieves us from the need to think about such things at all. – Holger Jul 15 '16 at 10:07
  • @Holger "Still, when replacing it with a lambda expression, you’ll get a singleton, even if the object escapes." Not exactly right. It ends up getting turned into a `static` method attached to the declaring class, e.g. `MyClass.lambda$1` and invoked using the bytecode instruction `invokedynamic`. Regardless, lambdas are much, much more efficient than anonymous classes, so I'd agree that it's much better to use them over anonymous classes. – Brian Aug 10 '16 at 16:15
  • 1
    The `invokedynamic` instruction has the semantic of requesting an instance of the specified functional interface which will invoke the synthetic method, e.g. `MyClass.lambda$1`. It's up to the JRE, how the instance is created. The *current JRE* will provide a singleton behavior for stateless lambas, i.e. subsequent executions of the `invokedynamic` instruction will produce the same instance. – Holger Aug 15 '16 at 09:44
  • @Holger Ah, I understand your use of "singleton" now, my mistake. Thanks for the clarification! – Brian Aug 15 '16 at 16:00
  • @Brian Is it actual in Java 8, 11, and other modern versions of Java today? – Sergey Nemchinov Mar 01 '21 at 04:20
  • @SergeyNemchinov Yes! Escape analysis is still part of modern JVMs. I will update my answer with links to documentation for 8, 11, and 14. – Brian Mar 01 '21 at 13:16
1

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.

"Premature optimization is the root of all evil" - Knuth

pobrelkey
  • 5,853
  • 20
  • 29
1

Found this thread: Java anonymous class efficiency implications , you may find it interesting

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

You can check out my micro-benchmark at http://jdmaguire.ca/Code/Comparing.java & http://jdmaguire.ca/Code/OutsideComp.java. I ran this on various values for wordLen, sortTimes, and listLen. As well, the JVM is slow to warm-up so I shuffled the method calls around. Please don't judge me for the awful non-commented code. I program better than that in RL. And Microbenching marking is almost as evil and useless as premature optimization.

Community
  • 1
  • 1
Lan
  • 1,206
  • 1
  • 11
  • 26