JVM is a great choice! You might also want to check out WebAssembly too (full disclosure: I used to work on wasm). There aren't too many opcodes, and a lot of them are relatively simple math operations.
Given your experience at this level of abstraction, do you have any thoughts on what an ideal set of opcodes look like? E.g. something like Knuth's MMIX. Do you prefer RISC or CISC? What about RISC-V? How much do the designers of real-world micro-architectures talk to folks like you?
A bit of a tangent, I know, but it feels important.
I'm probably the wrong person to ask, to be honest. I was around for a lot of the design of Wasm, but wasn't really involved at that level.
That said, in my opinion the ideal set of opcodes really depends on your goal. There were many goals For Wasm, but I think there was a focus on keeping it small and simple. Originally it was AST-based, but it was changed to a stack machine to reduce size. It was also designed to be AoT or JIT compiled, so the opcode layout is not particularly friendly to hardware decoding or interpreters (although people have made some very high quality Wasm interpreters).
And of course there were a lot of discussions and disagreements about the best way forward: AST vs. register VM vs. stack machine. Structured control flow vs. goto. How to handle unreachable code. How to store integer literals (LEB vs. prefix byte). What the text format should look like (sexprs vs. ...?) etc.
However there were a lot of discussions that were not in those meetings and were in smaller groups or held in GitHub issues or PRs. Most of these were in https://github.com/webassembly/design.
You're right about wasm not being too complicated, I've learned this when watching David Beazley coding a WebAssembly interpreter in under an hour. And it doesn't require any knowledge about it beforehand.
Once you get it working, you could run things like JSLinux! https://bellard.org/jslinux/vm.html?url=win2k.cfg&mem=192&gr...