Virtual threads are Java's new garbage collector

Virtual threads is one of the most discussed Java features in connection with the next LTS (Java 21) release coming very soon.

One can ask: Will it solve all my problems with concurrency scalability? Are there any risks if I start using it? And the obvious answer is: It depends. You may have already invested in scalability of your application by deciding to use reactive programming despite its additional code complexity. There may be problems applying virtual threads blindly. For example, you should not put virtual threads in a thread pool, so if you use a thread pool, you may need to analyse if you still need it.

But you can ask in general: Is it a good idea that the JVM encourages you to stop accessing the actual platform threads in favour of a new abstraction that looks very much like the platform thread, but behaves differently? In my opinion, Virtual threads fit into Java in a way similar to how the garbage collector does.

The presence of a garbage collector in Java (or in any other language) provides an illusion of infinite memory. Of course, you can still get an OutOfMemoryError, but if you use your objects properly (e.g. not putting all of them in a global HashMap and keeping them there even though they are not needed anymore like I have seen in one codebase), the runtime can detect automatically that some of your objects are not reachable anymore and thus the memory they occupy can be freed and reused. This abstraction, used by programmers since the very first version of Java, simplifies the code and prevents many nasty errors (I do remember the malloc/free hell from my C programming times).

Sure, I know there are performance-sensitive applications (like in algorithmic trading where you have to optimise for latency) that avoid the garbage collection and prefer to manage their objects themselves, for example pre-allocating large arrays and using their elements later as needed. Actually, notorious library classes like ArrayList or StringBuilder do exactly the same: They pre-allocate to avoid having to re-allocate and free objects every time something is added/appended to them.

Now, similarly to what a garbage collector does with memory, Virtual threads provide an illusion of an (almost) infinite number of threads. What really happens is, in a typical application using one thread per incoming network request (like a REST endpoint), the JVM together with the I/O libraries can detect automatically when the platform thread would be blocked due to waiting for I/O. To prevent it, the (virtual) thread state is moved to the heap and the platform thread is reused to work on some other request.

So if your application (like the majority of applications IMHO) concentrates on processing an incoming request sequentially, relying on the Virtual threads feature to use the platform threads efficiently will be very similar to relying on the garbage collector to clean up the memory objects at the end of the request. On the other hand, if your code (e.g. a database) needs to work with fine-grained concurrency (multiple threads per incoming request), you can do so, preferably using Structured concurrency (because go statement considered harmful).

I hope this way of looking at the Virtual threads feature can help you when deciding where to use it and what to expect from it.

Let me know your thoughts on LinkedIn.

Back to all blog posts