In Erlang/BEAM the runtime partitions the memory into isolated heaps. Cross heap references are not possible. All code evaluation is done by the runtime so all objects created only refer to the local heap. If you send something over to another heap, it is copied. This is similar to how Unix processes have isolated heaps.
The JVM is one big heap and any object cam refer to any other object anywhere. The runtime itself doesn't prevent any references. Now if you implement a framework on top that creates partitioned heaps and allows copying - you can only get so far. There will always be holes and once you use any shared library outside your framework, all bets are off. This kind of isolation is very hard to retrofit. Python has the same limitation (as Java).
A similar thing happens with lightweight threads. The Erlang runtime decides which heap/process (each heap is associated with a process) gets to run for how many steps. Then it can run another process for some steps. The multiplexing is preemptive so no tight loop can starve other processes. With user threads in Java, the code is supposed to call
yield
or something like that every so often and only then control goes back to the main loop which will schedule another thread. You can have starvation and latency spikes. Again this is hard to retrofit. The only preemptive scheduling available in JVM uses the (heavy) system threads.
FWIW, I think the Erlang isolated heap lightweight process model is great. Go copied the lightweight process (with preemptive scheduling) but it still uses a single heap.