p-System never did JIT. Remember this was for machines with 64 K of memory. Not 64 MB. But 64 K! The p-Code was much more compact than native code. Interpreting most code was plenty fast for most things -- even on the slow (under 10 MHz) clock speeds of the era. Critical operations could be written in assembler. The p-System had its own assembler for native code.
JVM is a whole other world. Adding GC is a whole new dimension in complexity. But decades of research have gone into the JVM making it the amazing runtime platform it is today.
https://en.wikipedia.org/wiki/Pascal_MicroEngine
https://en.wikipedia.org/wiki/Corvus_Systems
Adding a GC to bytecode systems, is what Xerox PARC did with their Interlisp-D, Smalltalk and Mesa/Cedar workstations.
The CPUs were microcoded and as part of the boot process they would load the respective hardware interpreter for the environment being booted into the workstation.
A similar idea was explored in one of Oberon's implementations, where instead of using straight native code like on the Ceres workstation, the modules would have a compact representation, JITed into native code on module load.
See section 7, Machine-Independent Mobile Code on http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.90....
Taos articles link at the bottom, Byte, IEEE, Edge etc.