Fwiw, the original implementation of threads in Java were 'green' threads (it was project Green after all). These were implemented in SunOS (vs System V) using setjmp/longjmp and an some auto-yield logic in the JVM (basically when you did certain things it would start that thing and yield your thread while it completed). When I started I had the most System V experience so I ported it over the Solaris to get to what was affectionately called the 'hello hello hello world world world' stage. The choice to use the Solaris native threads over green threads was motivated by the work that had gone on in the Solaris kernel to do CPU scheduling efficiently. That gave Java multiprocessing for 'free'. Except it exposed a bunch of bugs in the mutex / synchronized code when you actually got parallel execution :-).
The magic, as the author points out, is dynamic stack allocation. If you disassociate a thread with a required stack size, then you are back to using just the amount of memory that your object heap and reference stores are using. GC gets to be trickier to of course.
This should nominally be a non-issue on 64 bit address machines as you only need map the pages that actually have data in them (minimum one page) but that still puts a huge load on the page tables. This is especially true with huge pages which are used to avoid tlb thrashing and to save on the number of levels you have to go through to get back the physical address of the page you are trying to access.
Its the kind of optimization problem that systems folks really like to dig into and optimize around.