If one needs different JVMs for different architectures I can't figure out what is the logic behind introducing this concept. In other languages we need different compilers for different machines, but in Java we require different JVMs so what is the logic behind introducing the concept of a JVM or this extra step??
Sign up
- Anybody can ask a question
- Anybody can answer
- The best answers are voted up and rise to the top
|
The logic is that JVM bytecode is a lot simpler than Java source code. Compilers can be thought of, at a highly abstract level, as having three basic parts: parsing, semantic analysis, and code generation. Parsing consists of reading the code and turning it into a tree representation inside the compiler's memory. Semantic analysis is the part where it analyzes this tree, figures out what it means, and simplifies all the high-level constructs down to lower-level ones. And code generation takes the simplified tree and writes it out into a flat output. With a bytecode file, the parsing phase is greatly simplified, since it's written in the same flat byte stream format that the JIT uses, rather than a recursive (tree-structured) source language. Also, a lot of the heavy lifting of the semantic analysis has already been performed by the Java (or other language) compiler. So all it has to do is stream-read the code, do minimal parsing and minimal semantic analysis, and then perform code generation. This makes the task the JIT has to perform a lot simpler, and therefore a lot faster to execute, while still preserving the high-level metadata and semantic information that makes it possible to theoretically write single-source, cross-platform code. |
|||||||||||||||||||||
|
Intermediate representations of various sorts are increasingly common in compiler / runtime design, for a few reasons. In Java's case, the number one reason initially was probably portability: Java was heavily marketed initially as "Write Once, Run Anywhere". While you can achieve this by distributing the source code and using different compilers to target different platforms, this has a few downsides:
Other advantages of an intermediate representation include:
|
|||||
|
It sounds like you're wondering why we don't just distribute source code. Let me turn that question around: why don't we just distribute machine code? Clearly the answer here is that Java, by design, does not assume it knows what the machine is where your code will run; it could be a desktop, a super-computer, a phone, or anything in between and beyond. Java leaves room for the local JVM compiler to do its thing. In addition to increasing the portability of your code, this has the nice benefit of allowing the compiler to do things like take advantage of machine-specific optimizations, if they exist, or still produce at least working code if they do not. Things like SSE instructions or hardware acceleration can be used on only the machines that support them. Seen in this light, the reasoning for using byte-code over raw source code is clearer. Getting as close to raw machine language as possible allows us to realize or partially realize some of the benefits of machine code, such as:
Note that I don't mention faster execution. Both source code and byte code are or can (in theory) be fully compiled to the same machine code for actual execution. Additionally, byte code allows for some improvements over machine code. Of course there are the platform independence and hardware-specific optimizations I mentioned earlier, but there are also things like servicing the JVM compiler to produce new execution paths from old code. This can be to patch security issues, or if new optimizations are discovered, or to take advantage of new hardware instructions. In practice it's rare to see big changes this way, because it can expose bugs, but it is possible, and it's something that happens in small ways all the time. |
||||
|
The sense is that compiling from byte code to machine code is faster than interpreting your original code to machine code just in time. But we need interpretations to make our application cross-platform, because we want to use our original code on every platform without changes and without any preparations(compilations). So first javac compiles our source to byte code, then we can run this byte code anywhere and it will be interpreted by Java Virtual Machine to machine code more quickly. The answer: it saves time. |
||||
|
In addition to the advantages that other people have pointed out, bytecode's a lot smaller, so it's easier to distribute and update and takes up less space in the target environment. This is especially important in heavily space-constrained environments. It also makes it easier to protect copyrighted source code. |
|||
|
There seem to be at least two different possible questions here. One is really about compilers in general, with Java basically just an example of the genre. The other is more specific to Java the specific byte codes it uses. Compilers in generalLet's first consider the general question: why would a compiler use some an intermediate representation in the process of compiling source code to run on some particular processor? Complexity ReductionOne answer to that is fairly simple: it converts an O(N * M) problem into an O(N + M) problem. If we're given N source languages, and M targets, and each compiler is completely independent, then we need N * M compilers to translate all those source languages to all those targets (where a "target" is something like a combination of a processor and OS). If, however, all those compilers agree on a common intermediate representation, then we can have N compiler front ends that translate the source languages to the intermediate representation, and M compiler back ends that translate the intermediate representation to something suitable for a specific target. Problem SegmentationBetter still, it separates the problem into two more or less exclusive domains. People who know/care about language design, parsing and things like that can concentrate on compiler front ends, while people who know about instruction sets, processor design, and things like that can concentrate on the back end. So, for example, given something like LLVM, we have lots of front ends for various different languages. We also have back-ends for lots of different processors. A language guy can write a new front-end for his language, and quickly support lots of targets. A processor guy can write a new back-end for his target without dealing with language design, parsing, etc. Separating compilers into a front end and back end, with an intermediate representation to communicate between the two isn't original with Java. It's been pretty common practice for a long time (since well before Java came along, anyway). Distribution ModelsTo the extent that Java added anything new in this respect, it was in the distribution model. In particular, even though compilers have been separated into front-end and back-end pieces internally for a long time, they were typically distributed as a single product. For example, if you bought a Microsoft C compiler, internally it had a "C1" and a "C2", which were the front-end and back-end respectively--but what you bought was just "Microsoft C" that included both pieces (with a "compiler driver" that coordinated operations between the two). Even though the compiler was built in two pieces, to a normal developer using the compiler it was just a single thing that translated from source code to object code, with nothing visible in between. Java, instead, distributed the front-end in the Java Development Kit, and the back-end in the Java Virtual Machine. Every Java user had a compiler back-end to target whatever system he was using. Java developers distributed code in the intermediate format, so when a user loaded it, the JVM did whatever was necessary to execute it on their particular machine. PrecedentsNote that this distribution model wasn't entirely new either. Just for example, the UCSD P-system worked similarly: compiler front ends produced P-code, and each copy of the P-system included a virtual machine that did what was necessary to execute the P-code on that particular target1. Java byte-codeJava byte code is quite similar to P-code. It's basically instructions for a fairly simple machine. That machine is intended to be an abstraction of existing machines, so it's fairly easy to translate quickly to almost any specific target. Ease of translation was important early on because the original intent was to interpret byte codes, much like P-System had done (and, yes, that's exactly how the early implementations worked). StrengthsJava byte code is easy for a compiler front-end to produce. If (for example) you have a fairly typical tree representing an equation (or similar) it's typically pretty easy to traverse the tree, and generate code fairly directly from what you find at each node. Java byte codes are quite compact--in most cases, much more compact than either the source code or machine code for most typical processors (and, especially for most RISC processors, such as the SPARC that Sun sold when they designed Java). This was particularly important at the time, because one major intent of Java was to support applets--code embedded in web pages that would be downloaded before execution--at a time when most people accessed the we via modems over phone lines at around 28.8 kilobits per second (though, of course, there were still quite a few people using older, slower modems). WeaknessesThe major weakness of Java byte codes is that they aren't particularly expressive. Although they can express the concepts present in Java pretty well, they don't work nearly so well for expressing concepts that aren't part of Java. Likewise, while it's easy to execute byte codes on most machines, it's much harder to that in a way that takes full advantage of any particular machine. For example, it's pretty routine that if you really want to optimize Java byte codes, you basically do some reverse engineering to translate them backwards from a machine-code like representation, and turn them back into SSA instructions (or something similar)2. You then manipulate the SSA instructions to do your optimization, then translate from there to something that targets the architecture you really care about. Even with this rather complex process, however, some concepts that are foreign to Java are sufficiently difficult to express that it's difficult to translate from some source languages into machine code that runs (even close to) optimally on most typical machines. SummaryIf you're asking about why to use intermediate representations in general, two major factors are:
If you're asking about the specifics of the Java byte codes, and why they chose this particular representation instead of some other one, then I'd say the answer largely comes back to their original intent and the limitations of the web at the time, leading to the following priorities:
Being able to represent many languages or execute optimally on a wide variety of targets were much lower priorities (if they were considered priorities at all).
|
|||||
|
Text Source Code is a structure that intends to be easy to be read and modified by a human. Byte code is a structure that intends to be easy to be read and executed by a machine. I notice that there haven't been any examples yet. Silly Pseudo Examples:
Of course byte code is not just about optimizations. A large part of it is about being able to execute code without having to care about complicated rules, like checking if the class contains a member called "foo" somewhere further down in the file when a method refers to "foo". |
||||
|
protected by gnat 6 hours ago
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?