The non-JIT interpreter does not convert bytecode to machine code. You can imagine the non-JIT bytecode interpreter working something like this (I will use pseudocode similar to Java):
int[] bytecodes = { ... }; int ip = 0; // instruction pointer while(true) { int code = bytecodes[ip]; switch(code) { case 0; // do something ip += 1; break; case 1: // do something else ip += 1; break; // and so on... } }
So, for each executable bytecode, the interpreter should get the code, include its value to decide what to do, and increase its "instruction pointer" before proceeding to the next iteration.
With JIT, all this overhead will be reduced to zero. He just took the contents of the corresponding switch branches (the parts that say "// do something"), put them together in memory and go to the beginning of the first. No software instruction pointer is required — only the CPU hardware instruction pointer. The bytecode is not retrieved from memory and does not switch to their values.
Writing a virtual machine is not difficult (unless it should be extremely high), and can be an interesting exercise. I did it once for an embedded project where the program code should be very compact.
source share