Concepts / Understanding the Java Virtual Machine

Understanding the Java Virtual Machine

Java bytecode is an intermediate form used to separate a Java program from the computer that executes it.

  • Programming

Why Java Separates the Program from the Computer

Imagine you have written a Java program and want people on many different kinds of computers to run it. You could hand out your original Java source and let each machine deal with it directly, but that approach ties the program to the details of whatever computer receives it. Java takes a different route. It uses an intermediate form called Java bytecode, and that form is what separates a Java program from the computer that eventually executes it.

This article follows one compiled Java program from its source code to a computer that has never had a Java compiler of its own. Along the way you will see where bytecode fits, why the compiler and the interpreter are separate jobs, and what it means for a new type of computer to be supported.

Following One Program Through the Pipeline

The source describes the execution path as a sequence. First, a Java program is compiled. The result is intermediate Java bytecode. Next, a bytecode interpreter written for a particular type of computer interprets that bytecode. Finally, that computer can run the compiled Java program. Before reading the steps, try to predict this: if a new type of computer appears, which of these stages would need to be built specially for it?

Java source programJava compilerdeals with JavaIntermediate bytecodethe compiler's resultBytecode interpreterwritten for a particularcomputerProgram runs on thecomputercompiled byproducesinterpreted bylets the computer run it
What are the stages a program passes through, from source code to running on a new machine, and where does the bytecode fit in that sequence?

Read the diagram from top to bottom. The compiler takes the Java program and produces bytecode. The bytecode is the artifact in the middle of the pipeline. The interpreter then reads that bytecode, and the computer runs the program through that interpreter. The stage that changes from one computer to another is the interpreter, not the compiler. This is the central design choice: support for a new computer is added through its interpreter rather than through a new Java compiler.

Two Separate Jobs: Compiler and Interpreter

The key separation in this design is between the Java compiler and the bytecode interpreter. The compiler works with Java itself, which is a complex high-level language. The interpreter works with the intermediate bytecode, and it is relatively small and simple compared with the compiler. Because the two jobs are different in size and difficulty, they can be built and maintained with different amounts of effort.

Java programinputBytecodeinputIntermediate bytecodeoutputRunning programoutputComplex high-levellanguagethe Java it handlesRelatively small andsimplecompared with the compiler
How do the job, input, output, and effort of the Java compiler differ from those of the bytecode interpreter?

Read the two columns side by side. Both the compiler and the interpreter start from something and produce something, but their inputs differ. The compiler starts from Java and must understand it. The interpreter starts from bytecode, which is an intermediate form and not the original Java. This is why the interpreter can be simpler: it does not need to understand the full Java language, only the bytecode it is given.

Definition

Java bytecode is an intermediate form used to separate a Java program from the computer that executes it.

Tracing a Program onto a New Computer

Now apply the pipeline to a concrete situation. Suppose a team has already compiled its Java program into bytecode, and now a new type of computer needs to run that program. The source states that such a computer does not need its own Java compiler in order to run compiled Java programs. Instead, it can use a Java bytecode interpreter written for that computer. The diagram below shows how one bytecode file can relate to several interpreters, each installed on a different kind of machine.

Windows interpreterCompiled bytecodefileLinux interpretermacOS interpreterread byread byread by
How does the same compiled bytecode file relate to the different interpreters installed on Windows, Linux, and macOS machines?
Worked example

Reaching a Computer That Has No Java Compiler of Its Own

A Java program has already been compiled into bytecode. A new type of computer must run it. Trace which stages are reused and which stage must be written for the new computer.

  1. 1
    Start with the compiled result

    The Java program was compiled once, and the result is intermediate Java bytecode. This stage does not need to be repeated for the new computer.

  2. 2
    Write an interpreter for the new computer

    A bytecode interpreter is written for this particular type of computer. This is the piece that is new.

  3. 3
    Interpret the bytecode

    The interpreter for the new computer reads and interprets the bytecode, so the compiled program can be carried out on that machine.

  4. 4
    Run the program

    With the interpreter in place, the new computer can run the compiled Java program.

Result

The new computer is supported by adding a bytecode interpreter for it, not by writing a new Java compiler.

Common Misreadings of the Bytecode Path

  • Common mistake

    Thinking the interpreter is the same size and complexity as the compiler

    Why it fails

    The source describes the interpreter as relatively small and simple compared with the compiler, because it works with bytecode rather than with the full Java language.

    Do this instead

    Treat the compiler as the larger job that handles Java, and the interpreter as the smaller job that handles bytecode.

  • Common mistake

    Assuming a new computer needs its own Java compiler

    Why it fails

    The source states that a new type of computer does not need its own Java compiler in order to run compiled Java programs.

    Do this instead

    Add a bytecode interpreter written for that computer, and use the existing compiled bytecode.

  • Common mistake

    Believing the original Java source is the thing that gets run on each machine

    Why it fails

    In this design the program is compiled into intermediate bytecode, and the interpreter works with that bytecode.

    Do this instead

    Follow the sequence: compile to bytecode, interpret the bytecode, then run the program.

Practice Tracing the Path

EASY

A developer compiles a Java program once and gives the bytecode to users on three different kinds of computers. Each computer has its own bytecode interpreter. Describe, in order, the stages each user's machine goes through, and state which stage the developer does and which stage each user's computer performs.

Hints
  • Start with the stage that turns Java into bytecode and ask who performs it.
  • Then ask which piece is written for each type of computer.
  • The source says the interpreter is the piece that differs between computers.

Key Takeaways

  • Java bytecode is an intermediate form used to separate a Java program from the computer that executes it.
  • The execution path runs in sequence: a Java program is compiled into bytecode, a bytecode interpreter written for a particular computer interprets that bytecode, and then the computer runs the compiled program.
  • The Java compiler deals with the complex high-level Java language, while the bytecode interpreter deals with the intermediate bytecode and is relatively small and simple by comparison.
  • A new type of computer does not need its own Java compiler; support for it is added by writing a bytecode interpreter for that computer.