With 1:1 OS threads you will never get millions of lightweight processes cheaply like the BEAM. Whilst the BEAM does use O/S threads for its schedulers the overhead of BEAM processes is extremely light vs OS threads:
An Erlang process is lightweight compared to threads and processes in operating systems.
A newly spawned Erlang process uses 326 words of memory.
The size includes 233 words for the heap area (which includes the stack). The garbage collector increases the heap as needed.
So the actual process overhead excluding the stack and heap is 93 words. This is why spawning millions of them is a non event.
Java requires 16Kb base overhead physical RAM for a thread that does nothing but sleep as explored here but it also allocates a minimum of 1Mb virtual ram.
Even with Java using OS threads the assumption is the code must be correct and it must cooperate and check for interruption. From the Java docs:
What if a thread goes a long time without invoking a method that throws InterruptedException? Then it must periodically invoke Thread.interrupted, which returns true if an interrupt has been received. For example:
for (int i = 0; i < inputs.length; i++) {
heavyCrunch(inputs[i]);
if (Thread.interrupted()) {
// We’ve been interrupted: no more crunching.
return;
}
}
The Interrupt Status Flag
The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking Thread.interrupt sets this flag. When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared. The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.
By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so. However, it’s always possible that interrupt status will immediately be set again, by another thread invoking interrupt.
Of course everyone’s Java code does this right?
So there is the stake through the heart of Java “preemption”. It is basically a stoneage co-operative multi tasking system, set a flag and pray your code is correct everywhere and yeilds to check the interrupt flag and extricates itself cleanly out of its global object graph mess by exception handling. At that point your Java reliability is a patent FAIL at a fundamental level.
In contrast the BEAM made deliberate design choices based on programs being faulty and still provide guaranteed preemption, process kill and cleanup.