Funnily enough, the first versions of Java did not support OS threads and only had green threads for a while. Supporting real threads was a big deal at the time as it allowed you to use more than 1 processor. Of course, processors were single core at the time and most computers only had one of those anyway. Java 1.1 laid the foundations for proper multi threading and green thread support was eventually removed with Java 1.3. With Java 1.5 we got the java.concurrent package which enabled doing more complicated things with locks and other synchronization primitives that were a bit less primitive & brittle than using the synchronized & volatile keywords. That includes implementing green threads on top of real threads. Which is what frameworks like vert.x and others have been doing for ages.
So, in a way we're coming full circle here with virtual threads re-using the thread API, which in turn reused the original green thread APIs in Java 1.0.
Since Java uses very little FFI, it will benefit greatly from this automagical “no more blockingness”, of course only when there is some other available work in the meanwhile. Servers are the best fit for that.
In a typical thread pool if you schedule more long running tasks than you have worker threads, you either have to queue your tasks waiting for a free worker or you have to spawn a new worker. If your tasks are CPU bound and you do not care about latency/fairness, queuing is what you want. If your tasks might do blocking operations, you might underuse your CPU, or you simply want more fairness and not simple FIFO execution; in this case you can spawn new threads but OS threads are costly to spawn and schedule beyond a certain number. Virtual threads are simply cheaper threads as the scheduling is done in userspace and the JVM can in theory be smart about it.