user8479978
user8479978

Reputation:

The cost of Runnable in HotSpot

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

Answers (1)

Speakjava
Speakjava

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

Related Questions