JVM itself would do async I/O as needed using whatever API the underlying OS provides. Your own I/O code would be straightforward and synchronous. Nothing will run out of threads.
The key point is concurrent calls. If you're calling out to multiple microservices or what have you (the case where you would actually hit 1M threads in the first place) you'll want to do that in a concurrent way with futures or fork/join and not in a synchronous style.
One could argue that automatic promise wrapping and an await keyword ends up giving you cleaner, more simple code (despite the coloring).
The reason I dislike async/await is that the code doesn't clearly express what it actually does. I also dislike asynchronicity built directly into the language. It's just one more piece of context you have to keep track of, and the more context you need in your head to understand the code, the greater the potential for bugs, imo. It is good that Java forces one to explicitly write out their intentions.
I really like the approach Java has chosen: keep the language itself as dumb as possible, but add all the niceties to the standard library and runtime.