Reputation:
Whenever I write async code in Java, I have to use a Runnable
(Function
, Callable
, ...) or a new lambda syntax. There is no guarantee it will be inlined by the compiler, unlike for example C++ templates.
In what cases can it be optimized by the compiler, meaning something more efficient than instantiating a Runnable
object? What about JIT? For example, stream operations, lazy init, callbacks?
If it's not optimized, can HotSpot manage millions of Runnable
instances without any significant overhead to GC? In general, should I ever be concerned by an extensive use of the lambdas and callbacks in an application?
Upvotes: 0
Views: 271
Reputation: 3400
To start with you need to understand what the javac compiler does and what the JVM does via the JIT compiler.
Runnable is an interface so you can either create a class that implements that interface and then pass an instance of it to the Thread
constructor or you can use an anonymous inner class (AIC). In that case, the javac
compiler will generate a synthetic class for you that implements Runnable
and create an instance for you.
C++ uses static, ahead of time (AOT) compilation and, as you say, can inline templates. The JVM uses adaptive, just in time (JIT) compilation. When a class file is loaded the bytecodes are interpreted until the JVM determines that there are hot spots in the code and compiles them to native instructions that can be cached. How aggressive the optimisations that are used depends on the JIT being used. The OpenJDK has two JITs, C1 and C2 (sometimes referred to as client and server). C1 compiles code more quickly but optimises less. C2 takes longer to compile but optimises more. The run()
method of your Runnable
will inlined if the compiler decides that's the best optimisation (meaning it most likely will if it's heavily used). We at Azul (I work for them) have recently released a new JVM JIT called Falcon based on LLVM which optimises even further.
Lambdas are a bit different. Any Lambda expression can be converted to an equivalent AIC and for the early implementation in JDK 8 this is how they were implemented, as syntactic sugar to an AIC. To optimise performance javac
now generates code that uses the invokedynamic
bytecode instead. By doing this it leaves the way a Lambda is implemented to the JVM rather than hard-coding it in the class file. The JVM may use an AIC, it may use a static method or some other implementation method. As a minor point, using a method reference rather than an explicit Lambda is very slightly better for performance.
For the GC aspect of your question, it depends on the profile of your code. If you are using millions of Runnable
objects I would be more concerned about the impact of the Thread
objects. If you're not pooling those then the GC overhead of creating and collecting millions of threads will be far more than that of the Runnable
objects. So long as the Runnable
objects can be collected in the Eden space the overhead is effectively zero.
Upvotes: 1