Java source code is compiled into bytecode for the JVM. But, how does JVM convert bytecode into machine code? Does it re-compile bytecode into machine code and then run it? Or does it simply just run the bytecode, directly?
4 Answers
Byte code usually cannot be run directly on the processor, since the hardware doesn't have the opcodes aload
, getfield
etc.
The JVM either interprets the byte code, i.e. it looks up the things to do on the hardware for each opcode in some internal data structure, or it compiles the byte code to real machine code and simply calls the compiled code when a Java routine is called. Which to do when is an interesting question and the subject of much optimization research.
Java source code is compiled into bytecode for the JVM.
That is not necessarily true. There is nothing in the Java Language Specification that prescribes any particular implementation strategy. The JLS only specifies what the results of running the code should be, but not how to achieve those results.
It is perfectly legal per the JLS to write an interpreter for Java. Or to compile it to native code. Or to compile it to CLI CIL. Or to compile it to ECMAScript.
Compiling Java to JVM bytecode is a possible implementation, but certainly not the only possible one. It is not even the only one that has been done. There have been compilers that compile Java straight to native code, for example (GCJ). There are compilers that compile Java to ECMAScript (e.g. GWT, J2CL). I've seen some toy examples that compile Java to Perl, C, PHP, Scheme, Parrot bytecode, C#, CIL, and many others.
While not necessarily all of these implement the full Java Language Specification, there is nothing fundamentally stopping them from doing so.
But, how does JVM convert bytecode into machine code?
It doesn't. At least it doesn't have to. It could also interpret it instead of converting it into machine code. Or it could convert it into a different language.
There is nothing in the Java Virtual Machine Specification that prescribes any particular implementation strategy. The JVMS only specifies what the results of running the code should be, but not how to achieve those results.
That is pretty common in such specifications, actually. Usually, the designers try to stay out of specifying any particular implementation and give the implementors as much leeway as possible. The more the specification constrains the implementation, the less room for optimizations there is.
So, the JVMS does not specify that the JVM must interpret the bytecode. Nor does it specify that the JVM must compile the bytecode. It only specifies what the bytecode means, not how it is to be executed.
This is sometimes called the AS-IF rule: as long as the result of running the code looks as if the code had been executed according to the rules of the specification, the implementor can do anything they want. One example is eliminating loops without side-effects. Since you can't actually tell whether the loop was executed or not, eliminating the loop is legal, because the result of the code will be just as if the loop had been executed.
Does it re-compile bytecode into machine code and then run it?
There is nothing in the JVMS that would forbid this, but there is also nothing in the JVMS that would enforce this.
Every implementor can freely choose how to implement the JVMS.
Or does it simply just run the bytecode, directly?
There is nothing in the JVMS that would forbid this, but there is also nothing in the JVMS that would enforce this.
Every implementor can freely choose how to implement the JVMS.
I will just give a few examples of existing or historic JVM implementations:
- The original Sun JVM was a pure interpreter. It never compiled anything, it always interpreted.
- Excelsior JET was a pure ahead-of-time compiler. It statically compiled JVM bytecode to native machine code ahead of time. (There was also a runtime library with a JIT compiler for code that could not be compiled ahead of time, for example because it was generated at runtime or loaded at runtime.) There was no interpreter, no code was ever interpreted.
- GCJ was a pure ahead-of-time compiler as well. It could compile Java-to-native, Java-to-JVM, and JVM-to-native.
- picoJava CPUs can execute JVM bytecode directly.
- Maxine Research VM is a meta-circular JVM that is itself completely written in Java.
- The Maxine Research VM uses two pure Just-in-time compilers with no interpreter: T1X is a fast, non-optimizing compiler, the second compiler C1X is a more aggressively optimizing JIT compiler, which was actually a Java rewrite of the C1 compiler from HotSpot.
- A later iteration added the Graal compiler, which was actually developed as part of Maxine, and later ported to HotSpot.
- Sometime along the way, they also experimented with a tracing JIT.
- I believe there were plans at some point to add an interpreter, but I am not sure one was ever developed.
- Eclipse OpenJ9 / IBM J9 interprets the bytecode for a while and keeps a counter of which code was executed how often. Once it has identified these so-called "hot spots", it compiles them to native machine code.
- Oracle HotSpot JVM interprets the bytecode for a while and keeps a counter of which code was executed how often. Once it has identified these so-called "hot spots", it compiles them to native machine code using one of three compilers.
I have written some stuff about several different ways of implementing a language here, in case you are interested: Understanding the differences: traditional interpreter, JIT compiler, JIT interpreter and AOT compiler.
-
Thank you for that [he gulps]... Hmmm... information overload... Maybe?– GratefulCommented Sep 8, 2021 at 12:16
-
@Grateful answers should be useful for everyone, not only for the OP. JWM has addressed some false assumptions you made and that is also valuable information. He could have summed it up as: "It depends on the JVM implementation" but then you would have asked why and how?– LaivCommented Sep 10, 2021 at 8:52
-
Thank you for your comments @Laiv. As a general rule of thumb, without knowing the background of the questioner, the answer should always assume "generality" and/or "common practices" to be the case. Unless specifically asked about, "exceptions" should not take up the majority of the answer... Otherwise, a normal 83-word explanation could turn into a 730-word presentation.– GratefulCommented Sep 10, 2021 at 9:06
-
Assumptions are the seed of all failure to me :-) . Ideally, the community should not make any assumptions about the questioner's background that were not introduced by the OP itself. False assumptions in the question lead to false assumptions in the answer what make'em poor answers. The first are often downvoted ASAP in this community unless some addresses the issue with a relatively well-documented answer. Has is the case.– LaivCommented Sep 10, 2021 at 9:25
-
Note that the checked answer, while true, is incomplete and as imprecise as the question is. It will take any reader or interested to do more research to don't fall into the false assumption that all the JVMS are the same and work the same way.– LaivCommented Sep 10, 2021 at 9:29
how does JVM convert bytecode into machine code?
The keyword you need is "JIT compilation"; JIT stands for "just in time", i.e. it runs immediately before trying to execute the bytecode.
See this explainer from Oracle.
Does it re-compile bytecode into machine code and then run it?
Usually yes.
simply just run the bytecode, directly?
It can't be run "directly" unless the processor itself supports Java bytecode (ARM Jazelle, never really a success), so if it's not compiled then it is interpreted, i.e. a program that looks at each bytecode individually and performs the appropriate action.
There’s more than one JVM, and they do it differently.
A good JVM will first interpret the byte code, and compile it when the same code is interpreted multiple times. It will use a simple, fast compiler for this that doesn’t produce very good code. When this code runs a lot, it will be compiled again with a slow compiler producing better code.
And all the time the JVM will take care that if new classes are loaded and virtual method calls could reach a new method, then code calling these methods will be recompiled.
-Xcomp
command line flag.