From 10,000 feet, the overall setup looks roughly the same. As in many situations, though, the devil is in the details.

There are a few key distinctions to make between the JVM approach and WASM's approach.

The first is that the JVM was a very high-level VM. It exposed a high-level typed bytecode with support for complex structured types, a sophisticated garbage collected heap model, and out-of-the-box system APIs with a huge surface area.

The second is related, in that the JVM's security model was very much mixed up with its type-safety guarantees. This lead to significant complexity in the implementation of its security model. There are places in the JVM spec where you literally have to walk up the java virtual stack, looking at security tokens along the way, to determine if a particular system call should pass through or not. All of this stemmed from a design perspective that very much derived from our ideas of best practices at the time.

The third is that the Java spec very much relied on load-time verification of strong interfaces across modules. This is one of the reasons for Java applets' famously poor startup times. When a single class-file was loaded, all of its dependencies must be loaded and its consumed interfaces type-checked, and this prompts type-validation of _those_ modules, transitively. Bytecode needed to be abstract-interpreted, the types of values on stack verified, and the types of interface methods that consumed those values verified.

The idea was that all of this up-front verification would allow for the later generation of safe optimized code without much effort. That idea didn't pan out as well as hoped.

I say none of this to knock Java. At the time of its conception, this truly was the industry's understanding of how to build a robust and secure virtual machine model. Java was truly a groundbreaking technology.

WASM benefits from design hindsight. It adopts a strategy of virtualizing the system at a very _low_ level. It's still strongly typed, but the number of types in the system are paltry (a few basic scalars). Its heap model is basically a massive array of scalar words. No garbage collection, no object graph, no weak pointers, complex finalization semantics, etc. etc. If your mind now wanders to the fact that WASM promises support for garbage collection to arrive soon, I'll point out that WASM's idea of garbage collection support amounts to little more than a few hooks to help track "heap pointers" on the stack, to allow for programs to implement their own custom collection approaches. The VM itself does not and will not specify an object graph model or garbage collections semantics over that.

WASM's security model is also much more coarse grained and simple. The default system-api surface area in WASM is the empty interface: a wasm module which is loaded and executed and provided no importable modules simply cannot affect anything except its own memory array. Any interaction with the system MUST go through an explicitly provided set of imports that the environment configures and passes in.

The VM itself doesn't specify any system API at all. This is developed separately as an independent spec. And here too, the surface area of the API is much smaller. It's modeled after the core Linux/POSIX-ish system APIs, and much easier to reason about.

The WASM bytecode is also far simpler. You still need to verify the bytecode, but the types are all scalars, and verifying the usage of module interfaces doesn't require any transitive checks across different modules, like Java does.

All this makes the entire system far simpler, easier to prove secure, and faster to execute.

Java was one of the first production attempts at making mobile virtual code a reality. It was built by some of the best minds of the time, and it is a fantastic piece of technology.

WASM is the result of two decades of hindsight knowledge, engineering experience, and practical experience in sandboxing a browser. It's no accident that while Java was an embedded+systems technology that got adapted to browsers, WASM is a technology that was born in browsers (as a dialect of Javascript!) and is moving out of that realm. WASM was forged in a crucible where downloading and executing random untrusted third party code was expected and routine (web pages). It was able to make better design choices because it was designed in a context where many of those design choices had been forced through sheer existential need.

WASM was also designed on an as-needed basis. ASM.JS was a toy project that turned out to be surprisingly adept at executing performant code. Long before WASM existed, ASM.JS was made performant enough to run Unreal Engine 4 on a browser. It was only after ASM.JS had proven itself again and again that WASM was derived as a formalization of it.

This bottom-up, engineering first approach is in stark contrast to the JVM's top-down design-first approach.

So what's the difference between the two? To summarize: no single thing, but instead a large collection of individual changes that work together as a whole to compose a system that is qualitatively more flexible, more performant, more secure, and easier to reason about.

And I'll emphasize again that this is not to knock the JVM or its designers. Those pioneers simply did not have access to the hindsight knowledge, the years and years of practical experience running untrusted code in a production sandbox, years of experience building JIT compilers (for javascript) and understanding what was important for fast execution (it wasn't load-time complexity for sure).